@playcademy/sandbox 0.8.1-beta.5 → 0.8.1-beta.7
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 +1367 -2279
- package/dist/constants.js +1 -1
- package/dist/server.js +1367 -2279
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -285,7 +285,7 @@ var init_platform = __esm(() => {
|
|
|
285
285
|
var PLATFORM_TIMEZONE = "America/New_York";
|
|
286
286
|
|
|
287
287
|
// ../constants/src/timeback.ts
|
|
288
|
-
var TIMEBACK_GRADES, TIMEBACK_SUBJECTS, ASSESSMENT_PURPOSES, PLAYCADEMY_ASSESSMENT_RESULT_METADATA_VERSION = 1,
|
|
288
|
+
var TIMEBACK_GRADES, TIMEBACK_SUBJECTS, ASSESSMENT_PURPOSES, PLAYCADEMY_ASSESSMENT_RESULT_METADATA_VERSION = 1, PLAYCADEMY_ASSESSMENT_CONTRACT_VERSION = 1, PLAYCADEMY_ASSESSMENT_RESULT_METADATA_KEY = "playcademyAssessment", PLAYCADEMY_ASSESSMENT_ITEM_RESULT_METADATA_KEY = "playcademyAssessmentItem", TIMEBACK_ASSESSMENT_REVIEW_REQUEST_LIMITS, TIMEBACK_ORG_SOURCED_ID = "PLAYCADEMY", TIMEBACK_ORG_NAME = "Playcademy Studios", TIMEBACK_ORG_TYPE = "department", TIMEBACK_COURSE_DEFAULTS, TIMEBACK_RESOURCE_DEFAULTS, TIMEBACK_COMPONENT_DEFAULTS, TIMEBACK_COMPONENT_RESOURCE_DEFAULTS, TIMEBACK_ASSESSMENT_RESPONSE_VALUE_MAX_LENGTH = 2048, TIMEBACK_GAME_METRIC_DECIMAL_PLACES, TIMEBACK_GAME_METRIC_COMPARISON_TOLERANCE;
|
|
289
289
|
var init_timeback2 = __esm(() => {
|
|
290
290
|
TIMEBACK_GRADES = [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13];
|
|
291
291
|
TIMEBACK_SUBJECTS = [
|
|
@@ -1128,7 +1128,7 @@ var package_default;
|
|
|
1128
1128
|
var init_package = __esm(() => {
|
|
1129
1129
|
package_default = {
|
|
1130
1130
|
name: "@playcademy/sandbox",
|
|
1131
|
-
version: "0.8.1-beta.
|
|
1131
|
+
version: "0.8.1-beta.7",
|
|
1132
1132
|
description: "Local development server for Playcademy game development",
|
|
1133
1133
|
type: "module",
|
|
1134
1134
|
exports: {
|
|
@@ -9240,26 +9240,6 @@ var init_v3 = __esm(() => {
|
|
|
9240
9240
|
var init_esm = __esm(() => {
|
|
9241
9241
|
init_v3();
|
|
9242
9242
|
});
|
|
9243
|
-
// ../types/src/timeback/assessment-runtime.ts
|
|
9244
|
-
var PLAYABLE_ASSESSMENT_SHAPES;
|
|
9245
|
-
var init_assessment_runtime = __esm(() => {
|
|
9246
|
-
PLAYABLE_ASSESSMENT_SHAPES = ["circle", "ellipse", "rect", "poly", "default"];
|
|
9247
|
-
});
|
|
9248
|
-
// ../types/src/timeback/diagnostic-routing.ts
|
|
9249
|
-
var DIAGNOSTIC_ROUTING_MANIFEST_VERSION = 1, DIAGNOSTIC_ROUTING_STATE_VERSION = 1, DIAGNOSTIC_TRACK_OUTCOMES;
|
|
9250
|
-
var init_diagnostic_routing = __esm(() => {
|
|
9251
|
-
DIAGNOSTIC_TRACK_OUTCOMES = [
|
|
9252
|
-
"provisional",
|
|
9253
|
-
"unresolved",
|
|
9254
|
-
"route-to-instruction",
|
|
9255
|
-
"not-assessed"
|
|
9256
|
-
];
|
|
9257
|
-
});
|
|
9258
|
-
// ../types/src/timeback/index.ts
|
|
9259
|
-
var init_timeback4 = __esm(() => {
|
|
9260
|
-
init_assessment_runtime();
|
|
9261
|
-
init_diagnostic_routing();
|
|
9262
|
-
});
|
|
9263
9243
|
|
|
9264
9244
|
// ../utils/src/uuid.ts
|
|
9265
9245
|
function isValidUUID(value) {
|
|
@@ -9283,6 +9263,26 @@ var UUID_REGEX;
|
|
|
9283
9263
|
var init_uuid = __esm(() => {
|
|
9284
9264
|
UUID_REGEX = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
|
|
9285
9265
|
});
|
|
9266
|
+
// ../types/src/timeback/assessment-runtime.ts
|
|
9267
|
+
var PLAYABLE_ASSESSMENT_SHAPES;
|
|
9268
|
+
var init_assessment_runtime = __esm(() => {
|
|
9269
|
+
PLAYABLE_ASSESSMENT_SHAPES = ["circle", "ellipse", "rect", "poly", "default"];
|
|
9270
|
+
});
|
|
9271
|
+
// ../types/src/timeback/diagnostic-routing.ts
|
|
9272
|
+
var DIAGNOSTIC_ROUTING_MANIFEST_VERSION = 1, DIAGNOSTIC_ROUTING_STATE_VERSION = 1, DIAGNOSTIC_TRACK_OUTCOMES;
|
|
9273
|
+
var init_diagnostic_routing = __esm(() => {
|
|
9274
|
+
DIAGNOSTIC_TRACK_OUTCOMES = [
|
|
9275
|
+
"provisional",
|
|
9276
|
+
"unresolved",
|
|
9277
|
+
"route-to-instruction",
|
|
9278
|
+
"not-assessed"
|
|
9279
|
+
];
|
|
9280
|
+
});
|
|
9281
|
+
// ../types/src/timeback/index.ts
|
|
9282
|
+
var init_timeback4 = __esm(() => {
|
|
9283
|
+
init_assessment_runtime();
|
|
9284
|
+
init_diagnostic_routing();
|
|
9285
|
+
});
|
|
9286
9286
|
|
|
9287
9287
|
// ../timeback/dist/engines/assessment-runtime/index.js
|
|
9288
9288
|
function qtiDirectedPairValidationIssue(values, contract) {
|
|
@@ -9562,6 +9562,25 @@ function assessmentReviewBankShortage(fulfillment) {
|
|
|
9562
9562
|
function assessmentFlowForPurpose(purpose) {
|
|
9563
9563
|
return purpose === "review" ? "item-submit" : "attempt-submit";
|
|
9564
9564
|
}
|
|
9565
|
+
function isObject(value) {
|
|
9566
|
+
return typeof value === "object" && value !== null;
|
|
9567
|
+
}
|
|
9568
|
+
function isTimebackGrade(value) {
|
|
9569
|
+
return typeof value === "number" && Number.isInteger(value) && GRADE_VALUES.includes(value);
|
|
9570
|
+
}
|
|
9571
|
+
function isAssessmentSessionTiming(value) {
|
|
9572
|
+
return isObject(value) && typeof value.runId === "string" && isValidUUID(value.runId) && typeof value.resumeId === "string" && isValidUUID(value.resumeId) && isNonNegativeDuration(value.activeSeconds) && (value.inactiveSeconds === undefined || isNonNegativeDuration(value.inactiveSeconds));
|
|
9573
|
+
}
|
|
9574
|
+
function isNonNegativeDuration(value) {
|
|
9575
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0;
|
|
9576
|
+
}
|
|
9577
|
+
function isAssessmentPurpose(value) {
|
|
9578
|
+
return ASSESSMENT_PURPOSES.includes(value);
|
|
9579
|
+
}
|
|
9580
|
+
function isAssessmentResponseValue(value) {
|
|
9581
|
+
const entries = Array.isArray(value) ? value : [value];
|
|
9582
|
+
return entries.length > 0 && entries.every((entry) => typeof entry === "string" && entry.length > 0 && entry.length <= TIMEBACK_ASSESSMENT_RESPONSE_VALUE_MAX_LENGTH);
|
|
9583
|
+
}
|
|
9565
9584
|
function compareCodeUnits(left, right) {
|
|
9566
9585
|
if (left < right) {
|
|
9567
9586
|
return -1;
|
|
@@ -10415,48 +10434,6 @@ function projectDiagnosticRoutingSnapshot(revision, state) {
|
|
|
10415
10434
|
}))
|
|
10416
10435
|
};
|
|
10417
10436
|
}
|
|
10418
|
-
function isObject(value) {
|
|
10419
|
-
return typeof value === "object" && value !== null;
|
|
10420
|
-
}
|
|
10421
|
-
function isTimebackGrade(value) {
|
|
10422
|
-
return typeof value === "number" && Number.isInteger(value) && GRADE_VALUES.includes(value);
|
|
10423
|
-
}
|
|
10424
|
-
function isAssessmentSessionTiming(value) {
|
|
10425
|
-
return isObject(value) && typeof value.runId === "string" && isValidUUID(value.runId) && typeof value.resumeId === "string" && isValidUUID(value.resumeId) && isNonNegativeDuration(value.activeSeconds) && (value.inactiveSeconds === undefined || isNonNegativeDuration(value.inactiveSeconds));
|
|
10426
|
-
}
|
|
10427
|
-
function isNonNegativeDuration(value) {
|
|
10428
|
-
return typeof value === "number" && Number.isFinite(value) && value >= 0;
|
|
10429
|
-
}
|
|
10430
|
-
function isAssessmentPurpose(value) {
|
|
10431
|
-
return ASSESSMENT_PURPOSES.includes(value);
|
|
10432
|
-
}
|
|
10433
|
-
function isAssessmentResponseValue(value) {
|
|
10434
|
-
const entries = Array.isArray(value) ? value : [value];
|
|
10435
|
-
return entries.length > 0 && entries.every((entry) => typeof entry === "string" && entry.length > 0 && entry.length <= TIMEBACK_ASSESSMENT_RESPONSE_VALUE_MAX_LENGTH);
|
|
10436
|
-
}
|
|
10437
|
-
function applyAssessmentItemResponseUpdate(current, update) {
|
|
10438
|
-
const next = { ...current };
|
|
10439
|
-
for (const [responseId, value] of Object.entries(update)) {
|
|
10440
|
-
if (value === null) {
|
|
10441
|
-
delete next[responseId];
|
|
10442
|
-
} else {
|
|
10443
|
-
next[responseId] = value;
|
|
10444
|
-
}
|
|
10445
|
-
}
|
|
10446
|
-
return Object.keys(next).length === 0 ? undefined : next;
|
|
10447
|
-
}
|
|
10448
|
-
function applyAssessmentResponseUpdate(current, update) {
|
|
10449
|
-
const next = structuredClone(current);
|
|
10450
|
-
for (const [questionId, questionUpdate] of Object.entries(update)) {
|
|
10451
|
-
const questionResponses = applyAssessmentItemResponseUpdate(next[questionId], questionUpdate);
|
|
10452
|
-
if (questionResponses === undefined) {
|
|
10453
|
-
delete next[questionId];
|
|
10454
|
-
} else {
|
|
10455
|
-
next[questionId] = questionResponses;
|
|
10456
|
-
}
|
|
10457
|
-
}
|
|
10458
|
-
return next;
|
|
10459
|
-
}
|
|
10460
10437
|
function assessmentResponseUpdateValidationMessage(assessment, update) {
|
|
10461
10438
|
const items = new Map(assessment.items.map((item) => [item.identifier, item]));
|
|
10462
10439
|
for (const [itemIdentifier, itemUpdate] of Object.entries(update)) {
|
|
@@ -10618,158 +10595,163 @@ function interactionResponseValidationMessage(itemIdentifier, interaction, respo
|
|
|
10618
10595
|
}
|
|
10619
10596
|
return null;
|
|
10620
10597
|
}
|
|
10621
|
-
function
|
|
10622
|
-
return {
|
|
10623
|
-
}
|
|
10624
|
-
function administeredItemIdentifiers(itemSubmissions) {
|
|
10625
|
-
return new Set(itemSubmissions.map((submission) => submission.itemIdentifier));
|
|
10626
|
-
}
|
|
10627
|
-
function itemAdministration(itemSubmissions, itemIdentifier) {
|
|
10628
|
-
return itemSubmissions.find((submission) => submission.itemIdentifier === itemIdentifier);
|
|
10629
|
-
}
|
|
10630
|
-
function responseValuesEqual(left, right) {
|
|
10631
|
-
if (left === undefined || right === undefined) {
|
|
10632
|
-
return left === right;
|
|
10633
|
-
}
|
|
10634
|
-
if (Array.isArray(left) || Array.isArray(right)) {
|
|
10635
|
-
return Array.isArray(left) && Array.isArray(right) && left.length === right.length && left.every((value, index) => value === right[index]);
|
|
10636
|
-
}
|
|
10637
|
-
return left === right;
|
|
10638
|
-
}
|
|
10639
|
-
function itemResponsesEqual(left, right) {
|
|
10640
|
-
const leftEntries = Object.entries(left ?? {});
|
|
10641
|
-
const rightEntries = Object.entries(right ?? {});
|
|
10642
|
-
return leftEntries.length === rightEntries.length && leftEntries.every(([identifier, value]) => responseValuesEqual(value, right?.[identifier]));
|
|
10598
|
+
function emptyAssessmentTranscript(attemptId, contentRevision) {
|
|
10599
|
+
return { version: 1, attemptId, contentRevision, administrations: [] };
|
|
10643
10600
|
}
|
|
10644
|
-
function
|
|
10645
|
-
|
|
10646
|
-
}
|
|
10647
|
-
function resolveAssessmentItemReplay(state, input) {
|
|
10648
|
-
const flow = assessmentFlowForPurpose(state.purpose);
|
|
10649
|
-
if (flow !== "item-submit") {
|
|
10601
|
+
function assessmentProgressAdministration(purpose, input) {
|
|
10602
|
+
if (purpose !== "mastery") {
|
|
10650
10603
|
return {
|
|
10651
|
-
|
|
10652
|
-
failure: assessmentFlowViolation("
|
|
10604
|
+
ok: false,
|
|
10605
|
+
failure: assessmentFlowViolation("Background progress is only available for mastery.")
|
|
10653
10606
|
};
|
|
10654
10607
|
}
|
|
10655
|
-
const
|
|
10656
|
-
if (
|
|
10657
|
-
const sameRequest = priorSubmission.itemIdentifier === input.itemIdentifier && itemResponseUpdateMatches(state.responses[input.itemIdentifier], input.responses);
|
|
10658
|
-
if (!sameRequest) {
|
|
10659
|
-
return {
|
|
10660
|
-
action: "conflict",
|
|
10661
|
-
failure: assessmentFlowViolation("This item submission ID was already used for a different request.", {
|
|
10662
|
-
submissionId: input.submissionId,
|
|
10663
|
-
itemIdentifier: input.itemIdentifier,
|
|
10664
|
-
committedItemIdentifier: priorSubmission.itemIdentifier,
|
|
10665
|
-
flow
|
|
10666
|
-
})
|
|
10667
|
-
};
|
|
10668
|
-
}
|
|
10608
|
+
const { administrations } = input.transcript;
|
|
10609
|
+
if (input.expectedResponseVersion !== administrations.length) {
|
|
10669
10610
|
return {
|
|
10670
|
-
|
|
10671
|
-
|
|
10672
|
-
responses: state.responses,
|
|
10673
|
-
submission: priorSubmission
|
|
10611
|
+
ok: false,
|
|
10612
|
+
failure: assessmentResponseVersionConflict(input.expectedResponseVersion, administrations.length)
|
|
10674
10613
|
};
|
|
10675
10614
|
}
|
|
10676
|
-
|
|
10615
|
+
const administration = administrations.find((entry) => entry.itemIdentifier === input.itemIdentifier);
|
|
10616
|
+
return administration ? { ok: true, submissionId: administration.submissionId } : {
|
|
10617
|
+
ok: false,
|
|
10618
|
+
failure: assessmentFlowViolation("The progress item must be present in the transcript.")
|
|
10619
|
+
};
|
|
10677
10620
|
}
|
|
10678
|
-
function
|
|
10679
|
-
const
|
|
10680
|
-
|
|
10681
|
-
|
|
10682
|
-
|
|
10683
|
-
|
|
10684
|
-
|
|
10685
|
-
|
|
10686
|
-
|
|
10621
|
+
function appendAssessmentAdministration(transcript, input) {
|
|
10622
|
+
const entry = {
|
|
10623
|
+
submissionId: input.submissionId,
|
|
10624
|
+
itemIdentifier: input.itemIdentifier,
|
|
10625
|
+
...input.routingNodeKey === undefined ? {} : { routingNodeKey: input.routingNodeKey },
|
|
10626
|
+
responses: Object.fromEntries(Object.entries(input.responses).filter((pair) => pair[1] !== null))
|
|
10627
|
+
};
|
|
10628
|
+
const priorIndex = transcript.administrations.findIndex((item) => item.submissionId === input.submissionId);
|
|
10629
|
+
const index = priorIndex === -1 ? transcript.administrations.length : priorIndex;
|
|
10630
|
+
if (input.expectedResponseVersion !== index) {
|
|
10687
10631
|
return {
|
|
10688
|
-
|
|
10689
|
-
failure: assessmentResponseVersionConflict(input.expectedResponseVersion,
|
|
10632
|
+
ok: false,
|
|
10633
|
+
failure: assessmentResponseVersionConflict(input.expectedResponseVersion, index)
|
|
10690
10634
|
};
|
|
10691
10635
|
}
|
|
10692
|
-
|
|
10693
|
-
const previousItemIdentifier = state.itemSubmissions.at(-1)?.itemIdentifier;
|
|
10694
|
-
const previousItemIndex = state.assessment.items.findIndex((item) => item.identifier === previousItemIdentifier);
|
|
10695
|
-
if (itemIndex === -1 || itemIndex <= previousItemIndex) {
|
|
10636
|
+
if (priorIndex !== -1 && canonicalJson(transcript.administrations[priorIndex]) !== canonicalJson(entry)) {
|
|
10696
10637
|
return {
|
|
10697
|
-
|
|
10698
|
-
failure: assessmentFlowViolation("
|
|
10699
|
-
itemIdentifier: input.itemIdentifier,
|
|
10700
|
-
previousItemIdentifier: previousItemIdentifier ?? null,
|
|
10701
|
-
flow: assessmentFlowForPurpose(state.purpose)
|
|
10702
|
-
})
|
|
10638
|
+
ok: false,
|
|
10639
|
+
failure: assessmentFlowViolation("This administration identity was reused with different answers.")
|
|
10703
10640
|
};
|
|
10704
10641
|
}
|
|
10705
|
-
const update = { [input.itemIdentifier]: input.responses };
|
|
10706
|
-
const updateMessage = assessmentResponseUpdateValidationMessage(state.assessment, update);
|
|
10707
|
-
if (updateMessage) {
|
|
10708
|
-
return { action: "reject", failure: invalidResponse(updateMessage) };
|
|
10709
|
-
}
|
|
10710
|
-
const responses = applyAssessmentResponseUpdate(state.responses, update);
|
|
10711
|
-
const validationMessage = assessmentResponseValidationMessage(state.assessment, responses);
|
|
10712
|
-
if (validationMessage) {
|
|
10713
|
-
return { action: "reject", failure: invalidResponse(validationMessage) };
|
|
10714
|
-
}
|
|
10715
10642
|
return {
|
|
10716
|
-
|
|
10717
|
-
|
|
10718
|
-
|
|
10719
|
-
responses,
|
|
10720
|
-
answered: Object.keys(responses[input.itemIdentifier] ?? {}).length > 0
|
|
10643
|
+
ok: true,
|
|
10644
|
+
index,
|
|
10645
|
+
transcript: priorIndex === -1 ? { ...transcript, administrations: [...transcript.administrations, entry] } : transcript
|
|
10721
10646
|
};
|
|
10722
10647
|
}
|
|
10723
|
-
function
|
|
10724
|
-
const
|
|
10725
|
-
|
|
10726
|
-
|
|
10727
|
-
|
|
10728
|
-
|
|
10729
|
-
|
|
10730
|
-
|
|
10731
|
-
|
|
10732
|
-
|
|
10733
|
-
|
|
10734
|
-
|
|
10735
|
-
|
|
10736
|
-
|
|
10737
|
-
|
|
10738
|
-
|
|
10739
|
-
|
|
10648
|
+
function prepareAssessmentTranscript(input) {
|
|
10649
|
+
const parsed = AssessmentTranscriptSchema.safeParse(input.transcript);
|
|
10650
|
+
if (!parsed.success) {
|
|
10651
|
+
throw new AssessmentTranscriptError("Invalid assessment transcript", 0, "INVALID_RESPONSE");
|
|
10652
|
+
}
|
|
10653
|
+
const transcript = parsed.data;
|
|
10654
|
+
if (transcript.attemptId !== input.attemptId || transcript.contentRevision !== input.contentRevision) {
|
|
10655
|
+
throw new AssessmentTranscriptError("Transcript does not belong to this attempt and revision");
|
|
10656
|
+
}
|
|
10657
|
+
const responses = {};
|
|
10658
|
+
const submissions = new Set;
|
|
10659
|
+
const items = new Set;
|
|
10660
|
+
let previousItemIndex = -1;
|
|
10661
|
+
for (const [index, entry] of transcript.administrations.entries()) {
|
|
10662
|
+
const itemIndex = input.assessment.items.findIndex((item) => item.identifier === entry.itemIdentifier);
|
|
10663
|
+
if (submissions.has(entry.submissionId) || items.has(entry.itemIdentifier)) {
|
|
10664
|
+
throw new AssessmentTranscriptError("Transcript contains a repeated administration or item", index);
|
|
10665
|
+
}
|
|
10666
|
+
if (itemIndex === -1) {
|
|
10667
|
+
throw new AssessmentTranscriptError("Transcript references an unknown assessment item", index, "INVALID_RESPONSE");
|
|
10668
|
+
}
|
|
10669
|
+
if (!input.routed && itemIndex <= previousItemIndex) {
|
|
10670
|
+
throw new AssessmentTranscriptError("Transcript items must follow the selected candidate order", index);
|
|
10671
|
+
}
|
|
10672
|
+
if (input.routed !== (entry.routingNodeKey !== undefined)) {
|
|
10673
|
+
throw new AssessmentTranscriptError("Transcript routing identity does not match the assessment flow", index);
|
|
10674
|
+
}
|
|
10675
|
+
const itemResponses = { [entry.itemIdentifier]: entry.responses };
|
|
10676
|
+
const validate2 = input.routed || input.allowIncomplete ? assessmentDraftResponseValidationMessage : assessmentResponseValidationMessage;
|
|
10677
|
+
const invalid = Object.keys(entry.responses).length === 0 ? null : validate2(input.assessment, itemResponses);
|
|
10678
|
+
if (invalid) {
|
|
10679
|
+
throw new AssessmentTranscriptError(invalid, index, "INVALID_RESPONSE");
|
|
10680
|
+
}
|
|
10681
|
+
submissions.add(entry.submissionId);
|
|
10682
|
+
items.add(entry.itemIdentifier);
|
|
10683
|
+
previousItemIndex = itemIndex;
|
|
10684
|
+
Object.assign(responses, itemResponses);
|
|
10685
|
+
}
|
|
10686
|
+
return { transcript, responses };
|
|
10687
|
+
}
|
|
10688
|
+
function completeAssessmentTranscript(input) {
|
|
10689
|
+
const itemSubmissions = [];
|
|
10690
|
+
let state;
|
|
10691
|
+
const ledger = [];
|
|
10692
|
+
if (input.manifest) {
|
|
10693
|
+
const initial = initializeDiagnosticRouting(input.manifest);
|
|
10694
|
+
if (!initial.ok) {
|
|
10695
|
+
throw new AssessmentTranscriptError("The pinned diagnostic routing manifest is invalid");
|
|
10696
|
+
}
|
|
10697
|
+
state = initial.state;
|
|
10698
|
+
}
|
|
10699
|
+
const scores = new Map(input.scores.map((score) => [score.itemIdentifier, score]));
|
|
10700
|
+
for (const [index, entry] of input.transcript.administrations.entries()) {
|
|
10701
|
+
const scoring = scores.get(entry.itemIdentifier);
|
|
10702
|
+
if (!scoring) {
|
|
10703
|
+
throw new AssessmentTranscriptError("Transcript item has no trusted grade", index);
|
|
10704
|
+
}
|
|
10705
|
+
if (input.manifest) {
|
|
10706
|
+
if (typeof scoring.isCorrect !== "boolean" || !entry.routingNodeKey) {
|
|
10707
|
+
throw new AssessmentTranscriptError("Diagnostic grading must be determinate", index);
|
|
10708
|
+
}
|
|
10709
|
+
const advanced = advanceDiagnosticRouting(input.manifest, ledger, {
|
|
10710
|
+
routingNodeKey: entry.routingNodeKey,
|
|
10711
|
+
itemIdentifier: entry.itemIdentifier,
|
|
10712
|
+
isCorrect: scoring.isCorrect
|
|
10713
|
+
});
|
|
10714
|
+
if (!advanced.ok) {
|
|
10715
|
+
throw new AssessmentTranscriptError("Transcript does not follow the pinned diagnostic route", index);
|
|
10716
|
+
}
|
|
10717
|
+
ledger.push(advanced.ledgerEntry);
|
|
10718
|
+
state = advanced.state;
|
|
10719
|
+
}
|
|
10720
|
+
itemSubmissions.push({
|
|
10721
|
+
submissionId: entry.submissionId,
|
|
10722
|
+
itemIdentifier: entry.itemIdentifier,
|
|
10723
|
+
responseVersion: index + 1,
|
|
10724
|
+
submittedAt: input.submittedAt,
|
|
10725
|
+
answered: Object.keys(entry.responses).length > 0,
|
|
10726
|
+
score: scoring.score,
|
|
10727
|
+
isCorrect: scoring.isCorrect,
|
|
10728
|
+
...scoring.grading ? { grading: scoring.grading } : {}
|
|
10729
|
+
});
|
|
10730
|
+
}
|
|
10731
|
+
if (input.terminal) {
|
|
10732
|
+
const count = itemSubmissions.length;
|
|
10733
|
+
const conventionalComplete = assessmentFlowForPurpose(input.purpose) === "item-submit" ? count > 0 : count === input.itemCount;
|
|
10734
|
+
const complete = state ? state.status === "ready-to-complete" && count > 0 : conventionalComplete;
|
|
10735
|
+
if (!complete) {
|
|
10736
|
+
throw new AssessmentTranscriptError("A complete administered transcript is required for submission", count);
|
|
10737
|
+
}
|
|
10738
|
+
}
|
|
10739
|
+
return { itemSubmissions, ledger, state };
|
|
10740
10740
|
}
|
|
10741
|
-
function
|
|
10742
|
-
|
|
10743
|
-
const committedUpdate = Object.keys(update).find((identifier) => administered.has(identifier));
|
|
10744
|
-
return committedUpdate ? assessmentFlowViolation("A submitted item response cannot be edited.", {
|
|
10745
|
-
itemIdentifier: committedUpdate,
|
|
10746
|
-
flow: assessmentFlowForPurpose(purpose)
|
|
10747
|
-
}) : null;
|
|
10741
|
+
function administeredItemIdentifiers(itemSubmissions) {
|
|
10742
|
+
return new Set(itemSubmissions.map((submission) => submission.itemIdentifier));
|
|
10748
10743
|
}
|
|
10749
|
-
function
|
|
10750
|
-
|
|
10751
|
-
if (flow !== "item-submit") {
|
|
10752
|
-
return null;
|
|
10753
|
-
}
|
|
10754
|
-
if (itemSubmissions.length > 0 && itemSubmissions.length <= itemCount) {
|
|
10755
|
-
return null;
|
|
10756
|
-
}
|
|
10757
|
-
return assessmentFlowViolation("At least one candidate must be administered before the review can be completed.", {
|
|
10758
|
-
flow,
|
|
10759
|
-
submittedItemCount: itemSubmissions.length,
|
|
10760
|
-
itemCount
|
|
10761
|
-
});
|
|
10744
|
+
function itemAdministration(itemSubmissions, itemIdentifier) {
|
|
10745
|
+
return itemSubmissions.find((submission) => submission.itemIdentifier === itemIdentifier);
|
|
10762
10746
|
}
|
|
10763
10747
|
function selectAssessmentCourseCandidate(candidates, subjectSpecified) {
|
|
10764
10748
|
if (candidates.length === 0) {
|
|
10765
10749
|
return { outcome: "none" };
|
|
10766
10750
|
}
|
|
10767
|
-
const
|
|
10768
|
-
if (
|
|
10751
|
+
const eligible = candidates.filter((candidate) => candidate.hasLiveTests);
|
|
10752
|
+
if (eligible.length === 0) {
|
|
10769
10753
|
return { outcome: "no_eligible_tests" };
|
|
10770
10754
|
}
|
|
10771
|
-
const withOpenAttempt = serviceable.filter((candidate) => candidate.hasOpenAttempt);
|
|
10772
|
-
const eligible = withOpenAttempt.length > 0 ? withOpenAttempt : serviceable;
|
|
10773
10755
|
const subjects = new Set(eligible.map((candidate) => candidate.subject)).size;
|
|
10774
10756
|
if (!subjectSpecified && subjects > 1) {
|
|
10775
10757
|
return { outcome: "ambiguous", subjects };
|
|
@@ -10781,12 +10763,9 @@ function assessmentAttemptTimestamp(value) {
|
|
|
10781
10763
|
const parsed = Date.parse(value);
|
|
10782
10764
|
return Number.isNaN(parsed) ? 0 : parsed;
|
|
10783
10765
|
}
|
|
10784
|
-
function newestAttempt(left, right, field) {
|
|
10785
|
-
const dateDifference = assessmentAttemptTimestamp(right[field]) - assessmentAttemptTimestamp(left[field]);
|
|
10786
|
-
return dateDifference || left.attemptId.localeCompare(right.attemptId);
|
|
10787
|
-
}
|
|
10788
10766
|
function compareNewestCompletedAssessmentAttempt(left, right) {
|
|
10789
|
-
|
|
10767
|
+
const dateDifference = assessmentAttemptTimestamp(right.scoreDate) - assessmentAttemptTimestamp(left.scoreDate);
|
|
10768
|
+
return dateDifference || left.attemptId.localeCompare(right.attemptId);
|
|
10790
10769
|
}
|
|
10791
10770
|
function compareAssessmentCatalogOrder(left, right) {
|
|
10792
10771
|
if (left.assessmentKey !== null && right.assessmentKey !== null) {
|
|
@@ -10809,31 +10788,22 @@ function orderRuntimeAssessmentTests(tests) {
|
|
|
10809
10788
|
return [...tests].toSorted(compareAssessmentCatalogOrder);
|
|
10810
10789
|
}
|
|
10811
10790
|
function selectRuntimeAssessment(liveTests, attempts) {
|
|
10812
|
-
const resumable = attempts.filter((attempt) => attempt.resumable !== false && (isAssessmentAttemptOpen(attempt) || isAssessmentAttemptAwaitingAward(attempt))).toSorted((left, right) => newestAttempt(left, right, "updatedAt"));
|
|
10813
|
-
if (resumable.length > 0) {
|
|
10814
|
-
return {
|
|
10815
|
-
kind: "resume",
|
|
10816
|
-
attempt: resumable[0],
|
|
10817
|
-
additionalResumableAttemptIds: resumable.slice(1).map((candidate) => candidate.attemptId)
|
|
10818
|
-
};
|
|
10819
|
-
}
|
|
10820
10791
|
const orderedTests = orderRuntimeAssessmentTests(liveTests);
|
|
10821
10792
|
if (orderedTests.length === 0) {
|
|
10822
10793
|
return null;
|
|
10823
10794
|
}
|
|
10824
|
-
const
|
|
10825
|
-
const exposedAssessmentKeys = new Set(
|
|
10795
|
+
const history = attempts.filter((attempt) => isAssessmentAttemptOpen(attempt) || isAssessmentAttemptAwaitingAward(attempt) || isAssessmentAttemptCompleted(attempt) || isAssessmentAttemptSuperseded(attempt));
|
|
10796
|
+
const exposedAssessmentKeys = new Set(history.map((attempt) => attempt.selectedAssessmentKey));
|
|
10826
10797
|
const unattempted = orderedTests.find((test) => !exposedAssessmentKeys.has(test.assessmentKey));
|
|
10827
10798
|
if (unattempted) {
|
|
10828
|
-
return {
|
|
10799
|
+
return { test: unattempted, reason: "unattempted" };
|
|
10829
10800
|
}
|
|
10830
|
-
const
|
|
10831
|
-
const latestTestIndex =
|
|
10801
|
+
const latest = history.toSorted(compareNewestCompletedAssessmentAttempt)[0];
|
|
10802
|
+
const latestTestIndex = latest ? orderedTests.findIndex((test) => test.assessmentKey === latest.selectedAssessmentKey) : -1;
|
|
10832
10803
|
if (latestTestIndex === -1) {
|
|
10833
|
-
return {
|
|
10804
|
+
return { test: orderedTests[0], reason: "first_live" };
|
|
10834
10805
|
}
|
|
10835
10806
|
return {
|
|
10836
|
-
kind: "start",
|
|
10837
10807
|
test: orderedTests[(latestTestIndex + 1) % orderedTests.length],
|
|
10838
10808
|
reason: "round_robin"
|
|
10839
10809
|
};
|
|
@@ -11091,9 +11061,6 @@ function reviewRequestFingerprint(request, policy) {
|
|
|
11091
11061
|
function reviewQuestionLineItemId(parentLineItemId, itemIdentifier) {
|
|
11092
11062
|
return deterministicUUID(["playcademy:review-question-line-item:v1", parentLineItemId, itemIdentifier].join("\x00"));
|
|
11093
11063
|
}
|
|
11094
|
-
function reviewQuestionResultId(parentAttemptId, childQuestionLineItemId) {
|
|
11095
|
-
return deterministicUUID(["playcademy:review-question-result:v1", parentAttemptId, childQuestionLineItemId].join("\x00"));
|
|
11096
|
-
}
|
|
11097
11064
|
function qtiQuestionByIdentifier(questions) {
|
|
11098
11065
|
const byIdentifier = new Map;
|
|
11099
11066
|
for (const entry of questions.questions) {
|
|
@@ -11653,11 +11620,8 @@ function isDiagnosticRoutingState(value) {
|
|
|
11653
11620
|
const nextValid = value.next === null || isRecord(value.next) && isNonEmptyString(value.next.stageKey) && isNonEmptyString(value.next.trackKey) && isNonEmptyString(value.next.nodeKey) && isNonEmptyString(value.next.itemIdentifier);
|
|
11654
11621
|
return stagesValid && tracksValid && nextValid && (value.status === "in-progress" && value.next !== null || value.status === "ready-to-complete" && value.next === null);
|
|
11655
11622
|
}
|
|
11656
|
-
function isDiagnosticRoutingSnapshot(value, revision) {
|
|
11657
|
-
return isRecord(value) && value.kind === "platform-routed-diagnostic" && value.revision === revision && (value.status === "in-progress" || value.status === "ready-to-complete") && (value.next === null || isRecord(value.next) && isNonEmptyString(value.next.stageKey) && isNonEmptyString(value.next.trackKey) && isNonEmptyString(value.next.nodeKey) && isNonEmptyString(value.next.itemIdentifier)) && Array.isArray(value.tracks) && value.tracks.every((track) => isRecord(track) && isNonEmptyString(track.trackKey) && (track.groupKey === undefined || isNonEmptyString(track.groupKey)) && ["pending", "active", "completed", "skipped"].includes(String(track.status)) && Number.isInteger(track.administeredCount) && Number(track.administeredCount) >= 0);
|
|
11658
|
-
}
|
|
11659
11623
|
function isRoutedDiagnosticAttemptMetadata(value, itemSubmissions, responseVersion) {
|
|
11660
|
-
if (!isRecord(value) || !isNonEmptyString(value.definitionId) || !isNonEmptyString(value.diagnosticKey) || !isNonEmptyString(value.testName) || !isNonEmptyString(value.routingRevision) || !Array.isArray(value.ledger) || !value.ledger.every(isDiagnosticRoutingLedgerEntry) ||
|
|
11624
|
+
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) {
|
|
11661
11625
|
return false;
|
|
11662
11626
|
}
|
|
11663
11627
|
const itemIdentifiers = new Set;
|
|
@@ -11711,19 +11675,7 @@ function isPlaycademyAssessmentResultMetadataV1(value) {
|
|
|
11711
11675
|
const review = metadata2.review;
|
|
11712
11676
|
const diagnostic = metadata2.diagnostic;
|
|
11713
11677
|
const routedDiagnostic = metadata2.purpose === "diagnostic" && diagnostic !== undefined && Array.isArray(metadata2.itemSubmissions) && Number.isInteger(metadata2.responseVersion) && isRoutedDiagnosticAttemptMetadata(diagnostic, metadata2.itemSubmissions, metadata2.responseVersion);
|
|
11714
|
-
return metadata2.version === 1 && typeof metadata2.activityId === "string" && isAssessmentPurpose(metadata2.purpose) && typeof metadata2.courseId === "string" && typeof metadata2.integrationId === "string" && typeof metadata2.enrollmentId === "string" && Boolean(selectedTest) && (selectedTest?.assessmentKey === undefined || isNonEmptyString(selectedTest.assessmentKey)) && typeof selectedTest?.identifier === "string" && typeof selectedTest.contentRevision === "string" && Number.isInteger(metadata2.attemptNumber) && Number.isInteger(metadata2.responseVersion) && Boolean(metadata2.responses) && typeof metadata2.responses === "object" && Array.isArray(metadata2.itemSubmissions) && metadata2.itemSubmissions.every(isAssessmentItemSubmission) && typeof metadata2.startedAt === "string" && typeof metadata2.updatedAt === "string" && (metadata2.finalizedAt === undefined || typeof metadata2.finalizedAt === "string") && isAssessmentAwardRecord(metadata2.award) && (metadata2.purpose === "mastery" ? isMasteryAttemptMetadata(mastery) : mastery === undefined) && (metadata2.purpose === "review" ? isReviewAttemptMetadata(review) : review === undefined) && (metadata2.purpose === "diagnostic" ? diagnostic === undefined || routedDiagnostic : diagnostic === undefined) && (metadata2.submissionId === undefined || typeof metadata2.submissionId === "string") && (metadata2.score === undefined || isAssessmentScore(metadata2.score)) && isCompletionMetadata(metadata2.completion, metadata2.purpose, routedDiagnostic);
|
|
11715
|
-
}
|
|
11716
|
-
function isPlaycademyReviewAssessmentItemResultMetadataV1(value) {
|
|
11717
|
-
if (!isRecord(value) || !isRecord(value.responses)) {
|
|
11718
|
-
return false;
|
|
11719
|
-
}
|
|
11720
|
-
return value.version === 1 && typeof value.parentAttemptId === "string" && typeof value.activityId === "string" && typeof value.integrationId === "string" && typeof value.enrollmentId === "string" && typeof value.bankRevision === "string" && typeof value.itemIdentifier === "string" && isAssessmentStandardRef(value.standard) && typeof value.deliveredAt === "string" && Number.isInteger(value.responseVersion) && Object.values(value.responses).every(isAssessmentResponseValue) && (value.submissionId === undefined || typeof value.submissionId === "string") && (value.isCorrect === undefined || value.isCorrect === null || typeof value.isCorrect === "boolean");
|
|
11721
|
-
}
|
|
11722
|
-
function isPlaycademyDiagnosticAssessmentItemResultMetadataV1(value) {
|
|
11723
|
-
if (!isRecord(value) || !isRecord(value.responses)) {
|
|
11724
|
-
return false;
|
|
11725
|
-
}
|
|
11726
|
-
return value.version === 1 && isNonEmptyString(value.parentAttemptId) && isNonEmptyString(value.activityId) && isNonEmptyString(value.integrationId) && isNonEmptyString(value.enrollmentId) && isNonEmptyString(value.definitionId) && isNonEmptyString(value.diagnosticKey) && isNonEmptyString(value.routingRevision) && isNonEmptyString(value.contentRevision) && isNonEmptyString(value.stageKey) && isNonEmptyString(value.trackKey) && isNonEmptyString(value.routingNodeKey) && isNonEmptyString(value.itemIdentifier) && isNonEmptyString(value.deliveredAt) && Number.isInteger(value.responseVersion) && Number(value.responseVersion) > 0 && Object.values(value.responses).every(isAssessmentResponseValue) && isNonEmptyString(value.submissionId) && typeof value.isCorrect === "boolean";
|
|
11678
|
+
return metadata2.version === 1 && typeof metadata2.activityId === "string" && isAssessmentPurpose(metadata2.purpose) && typeof metadata2.courseId === "string" && typeof metadata2.integrationId === "string" && typeof metadata2.enrollmentId === "string" && Boolean(selectedTest) && (selectedTest?.assessmentKey === undefined || isNonEmptyString(selectedTest.assessmentKey)) && typeof selectedTest?.identifier === "string" && typeof selectedTest.contentRevision === "string" && Number.isInteger(metadata2.attemptNumber) && Number.isInteger(metadata2.responseVersion) && Boolean(metadata2.responses) && typeof metadata2.responses === "object" && Array.isArray(metadata2.itemSubmissions) && metadata2.itemSubmissions.every(isAssessmentItemSubmission) && typeof metadata2.startedAt === "string" && typeof metadata2.updatedAt === "string" && (metadata2.finalizedAt === undefined || typeof metadata2.finalizedAt === "string") && isAssessmentAwardRecord(metadata2.award) && (metadata2.purpose === "mastery" ? isMasteryAttemptMetadata(mastery) : mastery === undefined) && (metadata2.purpose === "review" ? isReviewAttemptMetadata(review) : review === undefined) && (metadata2.purpose === "diagnostic" ? diagnostic === undefined || routedDiagnostic : diagnostic === undefined) && (metadata2.submissionId === undefined || typeof metadata2.submissionId === "string") && (metadata2.score === undefined || isAssessmentScore(metadata2.score)) && (metadata2.transcript === undefined || AssessmentTranscriptSchema.safeParse(metadata2.transcript).success) && isCompletionMetadata(metadata2.completion, metadata2.purpose, routedDiagnostic);
|
|
11727
11679
|
}
|
|
11728
11680
|
function playcademyAssessmentResultMetadata(value) {
|
|
11729
11681
|
if (!isRecord(value)) {
|
|
@@ -11743,12 +11695,6 @@ function playcademyAssessmentResultMetadata(value) {
|
|
|
11743
11695
|
diagnostic: { ...normalized.diagnostic, ledger: [] }
|
|
11744
11696
|
};
|
|
11745
11697
|
}
|
|
11746
|
-
if (isRecord(normalized.diagnostic) && Array.isArray(normalized.diagnostic.ledger) && normalized.diagnostic.ledger.length === 0 && normalized.diagnostic.routingSnapshots === undefined) {
|
|
11747
|
-
normalized = {
|
|
11748
|
-
...normalized,
|
|
11749
|
-
diagnostic: { ...normalized.diagnostic, routingSnapshots: [] }
|
|
11750
|
-
};
|
|
11751
|
-
}
|
|
11752
11698
|
if (isRecord(normalized.diagnostic) && isRecord(normalized.diagnostic.state)) {
|
|
11753
11699
|
const state = normalized.diagnostic.state;
|
|
11754
11700
|
const tracks = Array.isArray(state.tracks) ? state.tracks.map((track) => isRecord(track) ? {
|
|
@@ -11756,12 +11702,10 @@ function playcademyAssessmentResultMetadata(value) {
|
|
|
11756
11702
|
currentNodeKey: track.currentNodeKey ?? null,
|
|
11757
11703
|
terminal: track.terminal ?? null
|
|
11758
11704
|
} : track) : state.tracks;
|
|
11759
|
-
const routingSnapshots = Array.isArray(normalized.diagnostic.routingSnapshots) ? normalized.diagnostic.routingSnapshots.map((snapshot) => isRecord(snapshot) ? { ...snapshot, next: snapshot.next ?? null } : snapshot) : normalized.diagnostic.routingSnapshots;
|
|
11760
11705
|
normalized = {
|
|
11761
11706
|
...normalized,
|
|
11762
11707
|
diagnostic: {
|
|
11763
11708
|
...normalized.diagnostic,
|
|
11764
|
-
...routingSnapshots === undefined ? {} : { routingSnapshots },
|
|
11765
11709
|
state: {
|
|
11766
11710
|
...state,
|
|
11767
11711
|
...tracks === undefined ? {} : { tracks },
|
|
@@ -11771,6 +11715,16 @@ function playcademyAssessmentResultMetadata(value) {
|
|
|
11771
11715
|
};
|
|
11772
11716
|
}
|
|
11773
11717
|
const review = normalized.review;
|
|
11718
|
+
if (isRecord(normalized.transcript)) {
|
|
11719
|
+
const transcript = normalized.transcript;
|
|
11720
|
+
normalized = {
|
|
11721
|
+
...normalized,
|
|
11722
|
+
transcript: {
|
|
11723
|
+
...transcript,
|
|
11724
|
+
administrations: Array.isArray(transcript.administrations) ? transcript.administrations.map((entry) => isRecord(entry) ? { ...entry, responses: entry.responses ?? {} } : entry) : transcript.administrations ?? []
|
|
11725
|
+
}
|
|
11726
|
+
};
|
|
11727
|
+
}
|
|
11774
11728
|
if (isRecord(review) && isRecord(review.fulfillment) && review.fulfillment.status === "complete" && review.fulfillment.shortages === undefined) {
|
|
11775
11729
|
normalized = {
|
|
11776
11730
|
...normalized,
|
|
@@ -11785,29 +11739,7 @@ function playcademyAssessmentResultMetadata(value) {
|
|
|
11785
11739
|
}
|
|
11786
11740
|
return isPlaycademyAssessmentResultMetadataV1(normalized) ? normalized : null;
|
|
11787
11741
|
}
|
|
11788
|
-
|
|
11789
|
-
if (!isRecord(value)) {
|
|
11790
|
-
return null;
|
|
11791
|
-
}
|
|
11792
|
-
const metadata2 = value[PLAYCADEMY_ASSESSMENT_ITEM_RESULT_METADATA_KEY];
|
|
11793
|
-
if (!isRecord(metadata2)) {
|
|
11794
|
-
return null;
|
|
11795
|
-
}
|
|
11796
|
-
const normalized = metadata2.responses === undefined ? { ...metadata2, responses: {} } : metadata2;
|
|
11797
|
-
return isPlaycademyReviewAssessmentItemResultMetadataV1(normalized) ? normalized : null;
|
|
11798
|
-
}
|
|
11799
|
-
function playcademyDiagnosticAssessmentItemResultMetadata(value) {
|
|
11800
|
-
if (!isRecord(value)) {
|
|
11801
|
-
return null;
|
|
11802
|
-
}
|
|
11803
|
-
const metadata2 = value[PLAYCADEMY_ASSESSMENT_ITEM_RESULT_METADATA_KEY];
|
|
11804
|
-
if (!isRecord(metadata2)) {
|
|
11805
|
-
return null;
|
|
11806
|
-
}
|
|
11807
|
-
const normalized = metadata2.responses === undefined ? { ...metadata2, responses: {} } : metadata2;
|
|
11808
|
-
return isPlaycademyDiagnosticAssessmentItemResultMetadataV1(normalized) ? normalized : null;
|
|
11809
|
-
}
|
|
11810
|
-
var SCRIPT_SCHEMES, DATA_RASTER_IMAGE_PATTERN, QTI_MATHML_ALLOWED_TAGS, metadataSymbol, parser, SUPPORTED_QTI_INTERACTION_TYPES, URL_ATTRIBUTES, IMAGE_URL_ATTRIBUTES, INTERACTION_TYPES, EMPHASIS_TAGS, STRONG_TAGS, CONTENT_SKIPPED_TAGS, BLOCK_CONTENT_KINDS, GAP_MATCH_TOKEN_TAGS, STAGE_GRAPHIC_EXCLUDED_CONTAINERS, BLANK_SENTINEL = "", MARKED_BLANK_PATTERN, MARKED_BLANK_GLOBAL_PATTERN, MATCH_CORRECT_TEMPLATE = "http://www.imsglobal.org/question/qti_v3p0/rptemplates/match_correct", MAP_RESPONSE_TEMPLATE = "https://purl.imsglobal.org/spec/qti/v3p0/rptemplates/map_response", MAP_RESPONSE_TEMPLATES, RESPONSE_PROCESSING_TEMPLATES, VOID_BODY_ELEMENTS, URL_ATTRIBUTES2, IMAGE_URL_ATTRIBUTES2, VOID_CHOICE_ELEMENTS, NUMERIC_COMPARISON_ATTRIBUTES, RESPONSE_CARDINALITIES, RESPONSE_BASE_TYPES, POINT_INTERACTION_TYPES, INDEPENDENT_PROCESSING_TAGS, PRIVATE_PLAYABLE_KEYS, trimmedNonEmptyString, finiteNumber2, positiveFiniteNumber, IDENTIFIER_VALUE, DIRECTED_PAIR_VALUE, scoringMatchComparisonSchema, scoringNumericEqualComparisonSchema, scoringNumericEqualRoundedComparisonSchema, scoringRuleSchema, scoringRulesSchema, scoringItemSchema, assessmentScoringArtifactItemsSchema, assessmentScoringArtifactSchema, SUPPORTED_BASE_TYPES, SUPPORTED_CARDINALITIES, MATCH_CORRECT_TEMPLATES, UUID_PATTERN, COMMON_CORE_MATH_FRAMEWORK_ALIASES, COMMON_CORE_ELA_FRAMEWORK_ALIASES, COMMON_CORE_FRAMEWORK_ALIASES, ASSESSMENT_ATTEMPT_OPEN, ASSESSMENT_ATTEMPT_COMPLETED, ASSESSMENT_ATTEMPT_AWAITING_AWARD, ASSESSMENT_ATTEMPT_SUPERSEDED, ASSESSMENT_RUNTIME_ERROR_STATUS, ROUTING_KEY_MAX_LENGTH = 128, ROUTING_RESULT_KEY_MAX_LENGTH = 256, ROUTING_STAGE_LIMIT = 32, ROUTING_TRACK_LIMIT = 128, ROUTING_NODE_LIMIT = 1000, ROUTING_TERMINAL_LIMIT = 1000, ROUTING_GROUP_LIMIT = 128, ROUTING_PREDICATE_DEPTH_LIMIT = 8, ROUTING_EXPLORATION_STATE_LIMIT = 200000, RoutingKeySchema, RoutingResultKeySchema, DiagnosticTrackOutcomeSchema, DiagnosticActivationPredicateSchema, DiagnosticRoutingTransitionSchema, DiagnosticRoutingManifestV1Schema, GRADE_VALUES, POINT_RESPONSE_PATTERN, ZERO_WIDTH_CHARACTERS, CURLY_SINGLE_QUOTES, CURLY_DOUBLE_QUOTES, UNICODE_DASHES, ARITHMETIC_OPERATOR_SPACING, THOUSANDS_GROUP, REVIEW_SELECTION_POLICY_VERSION = "unseen-first-least-recently-delivered-v1", PLAYCADEMY_REVIEW_BANK_MANIFEST_METADATA_KEY = "playcademyReviewBank", PLAYCADEMY_REVIEW_BANK_MANIFEST_VERSION = 1, DEFAULT_REVIEW_SELECTION_POLICY, ASSESSMENT_RUNTIME_FIXTURE_BUNDLE_VERSION = 8, ASSESSMENT_CHILD_RESULT_KIND = "assessment-child-result", ASSESSMENT_CHILD_RESULT_VERSION = 1, ASSESSMENT_CHILD_RESULT_ID_NAMESPACE = "playcademy:assessment-child-result:v1", IdentitySchema, RecordedAtSchema, ResponseKeySchema, AssessmentResponseValueSchema, AssessmentScoreSchema, AssessmentStandardRefSchema, DiagnosticRoutingLedgerEntryV1Schema, AssessmentChildResultAttemptV1Schema, AssessmentChildResultAdministrationV1Schema, AssessmentChildResultGradingV1Schema, ReviewChildResultContextV1Schema, DiagnosticChildResultContextV1Schema, CommonPayloadShape, ReviewChildResultPayloadV1Schema, DiagnosticChildResultPayloadV1Schema, AssessmentChildResultPayloadV1Schema, AssessmentChildResultCommandV1Schema, RuntimeSubjectSchema, RuntimeGradeSchema, OptionalQueryGradeSchema, AssessmentResponseValueSchema2, AssessmentRuntimeIdentitySchema, ResponseKeySchema2, StartAssessmentBaseSchema, AssessmentStandardRefSchema2, LatestAssessmentFilterBaseSchema, LatestAssessmentFiltersSchema, LatestRuntimeAssessmentQuerySchema, StartAssessmentBodySchema, AssessmentPreparationReceiptSchema, StartAssessmentRequestBodySchema, SaveAssessmentBodySchema, SubmitAssessmentItemBodySchema, SubmitAssessmentBodySchema, FinalizeAssessmentBodySchema, PrepareRuntimeAssessmentRequestSchema, StartRuntimeAssessmentRequestSchema, SaveRuntimeAssessmentRequestSchema, SubmitAssessmentItemRuntimeRequestSchema, SubmitRuntimeAssessmentRequestSchema, FinalizeRuntimeAssessmentRequestSchema;
|
|
11742
|
+
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, GRADE_VALUES, 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, POINT_RESPONSE_PATTERN, Identity, AssessmentTranscriptSchema, AssessmentTranscriptError, 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, FixedChildResultContextV1Schema, DiagnosticChildResultContextV1Schema, CommonPayloadShape, ReviewChildResultPayloadV1Schema, FixedChildResultPayloadV1Schema, DiagnosticChildResultPayloadV1Schema, AssessmentChildResultPayloadV1Schema, AssessmentChildResultCommandV1Schema, RuntimeSubjectSchema, RuntimeGradeSchema, OptionalQueryGradeSchema, AssessmentResponseValueSchema2, AssessmentRuntimeIdentitySchema, ResponseKeySchema2, StartAssessmentBaseSchema, AssessmentStandardRefSchema2, LatestAssessmentFilterBaseSchema, LatestAssessmentFiltersSchema, LatestRuntimeAssessmentQuerySchema, StartAssessmentBodySchema, AssessmentPreparationReceiptSchema, StartAssessmentRequestBodySchema, SaveAssessmentBodySchema, RecordAssessmentProgressBodySchema, ResumeAssessmentBodySchema, ResumeRuntimeAssessmentRequestSchema, SubmitAssessmentItemBodySchema, SubmitAssessmentBodySchema, FinalizeAssessmentBodySchema, PrepareRuntimeAssessmentRequestSchema, StartRuntimeAssessmentRequestSchema, SaveRuntimeAssessmentRequestSchema, RecordRuntimeAssessmentProgressRequestSchema, SubmitAssessmentItemRuntimeRequestSchema, SubmitRuntimeAssessmentRequestSchema, FinalizeRuntimeAssessmentRequestSchema;
|
|
11811
11743
|
var init_assessment_runtime2 = __esm(() => {
|
|
11812
11744
|
init_timeback3();
|
|
11813
11745
|
init_timeback3();
|
|
@@ -11817,13 +11749,14 @@ var init_assessment_runtime2 = __esm(() => {
|
|
|
11817
11749
|
init_timeback3();
|
|
11818
11750
|
init_esm();
|
|
11819
11751
|
init_esm();
|
|
11752
|
+
init_src();
|
|
11753
|
+
init_uuid();
|
|
11754
|
+
init_esm();
|
|
11820
11755
|
init_timeback4();
|
|
11821
11756
|
init_src();
|
|
11822
11757
|
init_timeback3();
|
|
11823
11758
|
init_src();
|
|
11824
11759
|
init_uuid();
|
|
11825
|
-
init_src();
|
|
11826
|
-
init_uuid();
|
|
11827
11760
|
init_esm();
|
|
11828
11761
|
init_src();
|
|
11829
11762
|
init_uuid();
|
|
@@ -12119,6 +12052,7 @@ var init_assessment_runtime2 = __esm(() => {
|
|
|
12119
12052
|
ATTEMPT_ALREADY_SUBMITTED: 409,
|
|
12120
12053
|
ASSESSMENT_FLOW_VIOLATION: 409
|
|
12121
12054
|
};
|
|
12055
|
+
GRADE_VALUES = TIMEBACK_GRADES;
|
|
12122
12056
|
RoutingKeySchema = exports_external.string().trim().min(1).max(ROUTING_KEY_MAX_LENGTH);
|
|
12123
12057
|
RoutingResultKeySchema = exports_external.string().trim().min(1).max(ROUTING_RESULT_KEY_MAX_LENGTH);
|
|
12124
12058
|
DiagnosticTrackOutcomeSchema = exports_external.enum(DIAGNOSTIC_TRACK_OUTCOMES);
|
|
@@ -12176,8 +12110,31 @@ var init_assessment_runtime2 = __esm(() => {
|
|
|
12176
12110
|
resultKey: RoutingResultKeySchema
|
|
12177
12111
|
}).strict()).min(1).max(ROUTING_TERMINAL_LIMIT)
|
|
12178
12112
|
}).strict();
|
|
12179
|
-
GRADE_VALUES = TIMEBACK_GRADES;
|
|
12180
12113
|
POINT_RESPONSE_PATTERN = /^-?\d+(?:\.\d+)? -?\d+(?:\.\d+)?$/;
|
|
12114
|
+
Identity = exports_external.string().min(1).max(512).refine((value) => value.trim() === value);
|
|
12115
|
+
AssessmentTranscriptSchema = exports_external.object({
|
|
12116
|
+
version: exports_external.literal(1),
|
|
12117
|
+
attemptId: Identity,
|
|
12118
|
+
contentRevision: Identity,
|
|
12119
|
+
administrations: exports_external.array(exports_external.object({
|
|
12120
|
+
submissionId: Identity,
|
|
12121
|
+
itemIdentifier: Identity,
|
|
12122
|
+
routingNodeKey: Identity.optional(),
|
|
12123
|
+
responses: exports_external.record(Identity, exports_external.custom(isAssessmentResponseValue))
|
|
12124
|
+
}).strict()).max(1000)
|
|
12125
|
+
}).strict().refine((value) => new TextEncoder().encode(JSON.stringify(value)).length <= 256000, {
|
|
12126
|
+
message: "Assessment transcript exceeds 256 KB"
|
|
12127
|
+
});
|
|
12128
|
+
AssessmentTranscriptError = class AssessmentTranscriptError extends Error {
|
|
12129
|
+
index;
|
|
12130
|
+
code;
|
|
12131
|
+
constructor(message, index = 0, code = "ASSESSMENT_FLOW_VIOLATION") {
|
|
12132
|
+
super(message);
|
|
12133
|
+
this.name = "AssessmentTranscriptError";
|
|
12134
|
+
this.index = index;
|
|
12135
|
+
this.code = code;
|
|
12136
|
+
}
|
|
12137
|
+
};
|
|
12181
12138
|
ZERO_WIDTH_CHARACTERS = /[\u200B\uFEFF\u200C\u200D]/g;
|
|
12182
12139
|
CURLY_SINGLE_QUOTES = /[\u2018\u2019\u201A\u201B]/g;
|
|
12183
12140
|
CURLY_DOUBLE_QUOTES = /[\u201C\u201D\u201E\u201F]/g;
|
|
@@ -12269,6 +12226,10 @@ var init_assessment_runtime2 = __esm(() => {
|
|
|
12269
12226
|
bankRevision: IdentitySchema,
|
|
12270
12227
|
standard: AssessmentStandardRefSchema
|
|
12271
12228
|
}).strict();
|
|
12229
|
+
FixedChildResultContextV1Schema = exports_external.object({
|
|
12230
|
+
kind: exports_external.literal("fixed"),
|
|
12231
|
+
purpose: exports_external.enum(["mastery", "end_of_course", "diagnostic"])
|
|
12232
|
+
}).strict();
|
|
12272
12233
|
DiagnosticChildResultContextV1Schema = exports_external.object({
|
|
12273
12234
|
kind: exports_external.literal("platform-routed-diagnostic"),
|
|
12274
12235
|
definitionId: IdentitySchema,
|
|
@@ -12288,6 +12249,11 @@ var init_assessment_runtime2 = __esm(() => {
|
|
|
12288
12249
|
isCorrect: exports_external.boolean().optional(),
|
|
12289
12250
|
context: ReviewChildResultContextV1Schema
|
|
12290
12251
|
}).strict();
|
|
12252
|
+
FixedChildResultPayloadV1Schema = exports_external.object({
|
|
12253
|
+
...CommonPayloadShape,
|
|
12254
|
+
isCorrect: exports_external.boolean().optional(),
|
|
12255
|
+
context: FixedChildResultContextV1Schema
|
|
12256
|
+
}).strict();
|
|
12291
12257
|
DiagnosticChildResultPayloadV1Schema = exports_external.object({
|
|
12292
12258
|
...CommonPayloadShape,
|
|
12293
12259
|
isCorrect: exports_external.boolean(),
|
|
@@ -12303,7 +12269,8 @@ var init_assessment_runtime2 = __esm(() => {
|
|
|
12303
12269
|
});
|
|
12304
12270
|
AssessmentChildResultPayloadV1Schema = exports_external.union([
|
|
12305
12271
|
ReviewChildResultPayloadV1Schema,
|
|
12306
|
-
DiagnosticChildResultPayloadV1Schema
|
|
12272
|
+
DiagnosticChildResultPayloadV1Schema,
|
|
12273
|
+
FixedChildResultPayloadV1Schema
|
|
12307
12274
|
]);
|
|
12308
12275
|
AssessmentChildResultCommandV1Schema = exports_external.object({
|
|
12309
12276
|
kind: exports_external.literal(ASSESSMENT_CHILD_RESULT_KIND),
|
|
@@ -12380,9 +12347,19 @@ var init_assessment_runtime2 = __esm(() => {
|
|
|
12380
12347
|
StartAssessmentRequestBodySchema = exports_external.intersection(StartAssessmentBodySchema, exports_external.object({ preparationReceipt: AssessmentPreparationReceiptSchema.optional() }));
|
|
12381
12348
|
SaveAssessmentBodySchema = exports_external.object({
|
|
12382
12349
|
expectedResponseVersion: exports_external.number().int().nonnegative(),
|
|
12383
|
-
|
|
12350
|
+
transcript: AssessmentTranscriptSchema
|
|
12351
|
+
});
|
|
12352
|
+
RecordAssessmentProgressBodySchema = SaveAssessmentBodySchema.extend({
|
|
12353
|
+
assessmentToken: exports_external.string().min(1).max(32000),
|
|
12354
|
+
itemIdentifier: ResponseKeySchema2
|
|
12384
12355
|
});
|
|
12356
|
+
ResumeAssessmentBodySchema = exports_external.object({
|
|
12357
|
+
transcript: AssessmentTranscriptSchema.optional()
|
|
12358
|
+
});
|
|
12359
|
+
ResumeRuntimeAssessmentRequestSchema = AssessmentRuntimeIdentitySchema.extend(ResumeAssessmentBodySchema.shape);
|
|
12385
12360
|
SubmitAssessmentItemBodySchema = exports_external.object({
|
|
12361
|
+
assessmentToken: exports_external.string().min(1).max(32000),
|
|
12362
|
+
transcript: AssessmentTranscriptSchema,
|
|
12386
12363
|
expectedResponseVersion: exports_external.number().int().nonnegative(),
|
|
12387
12364
|
submissionId: exports_external.string().trim().min(1),
|
|
12388
12365
|
itemIdentifier: ResponseKeySchema2,
|
|
@@ -12390,6 +12367,7 @@ var init_assessment_runtime2 = __esm(() => {
|
|
|
12390
12367
|
responses: exports_external.record(ResponseKeySchema2, AssessmentResponseValueSchema2.nullable())
|
|
12391
12368
|
});
|
|
12392
12369
|
SubmitAssessmentBodySchema = exports_external.object({
|
|
12370
|
+
transcript: AssessmentTranscriptSchema,
|
|
12393
12371
|
expectedResponseVersion: exports_external.number().int().nonnegative(),
|
|
12394
12372
|
submissionId: exports_external.string().trim().min(1),
|
|
12395
12373
|
session: exports_external.custom(isAssessmentSessionTiming, {
|
|
@@ -12408,6 +12386,7 @@ var init_assessment_runtime2 = __esm(() => {
|
|
|
12408
12386
|
PrepareRuntimeAssessmentRequestSchema = AssessmentRuntimeIdentitySchema.and(StartAssessmentBodySchema);
|
|
12409
12387
|
StartRuntimeAssessmentRequestSchema = AssessmentRuntimeIdentitySchema.and(StartAssessmentRequestBodySchema);
|
|
12410
12388
|
SaveRuntimeAssessmentRequestSchema = AssessmentRuntimeIdentitySchema.extend(SaveAssessmentBodySchema.shape);
|
|
12389
|
+
RecordRuntimeAssessmentProgressRequestSchema = AssessmentRuntimeIdentitySchema.extend(RecordAssessmentProgressBodySchema.shape);
|
|
12411
12390
|
SubmitAssessmentItemRuntimeRequestSchema = AssessmentRuntimeIdentitySchema.extend(SubmitAssessmentItemBodySchema.shape);
|
|
12412
12391
|
SubmitRuntimeAssessmentRequestSchema = AssessmentRuntimeIdentitySchema.extend(SubmitAssessmentBodySchema.shape).extend({
|
|
12413
12392
|
appName: exports_external.string().trim().min(1),
|
|
@@ -100207,22 +100186,6 @@ var init_locks = __esm(() => {
|
|
|
100207
100186
|
});
|
|
100208
100187
|
|
|
100209
100188
|
// ../api-core/src/utils/assessment-runtime-lock.util.ts
|
|
100210
|
-
async function crossAssessmentAttemptLockBarrier(lock, db2, attemptId) {
|
|
100211
|
-
const maxAttempts = 5;
|
|
100212
|
-
for (let attempt = 1;attempt <= maxAttempts; attempt += 1) {
|
|
100213
|
-
try {
|
|
100214
|
-
await lock(db2, [assessmentRuntimeAttemptLockKey(attemptId)], async () => {
|
|
100215
|
-
return;
|
|
100216
|
-
});
|
|
100217
|
-
return;
|
|
100218
|
-
} catch (error88) {
|
|
100219
|
-
if (!(error88 instanceof ServiceUnavailableError) || attempt === maxAttempts) {
|
|
100220
|
-
throw error88;
|
|
100221
|
-
}
|
|
100222
|
-
await sleep(25);
|
|
100223
|
-
}
|
|
100224
|
-
}
|
|
100225
|
-
}
|
|
100226
100189
|
function normalizeKeys(keys) {
|
|
100227
100190
|
return [...new Set(keys)].toSorted();
|
|
100228
100191
|
}
|
|
@@ -100295,17 +100258,6 @@ var init_assessment_runtime_lock_util = __esm(() => {
|
|
|
100295
100258
|
});
|
|
100296
100259
|
|
|
100297
100260
|
// ../api-core/src/utils/timeback-assessment-runtime.util.ts
|
|
100298
|
-
function assessmentPreparationContentionError(expectedResponseVersion, responseVersion, kind) {
|
|
100299
|
-
if (expectedResponseVersion === responseVersion) {
|
|
100300
|
-
return new ServiceUnavailableError(`The ${kind} response state changed repeatedly while this item was being prepared. Try again shortly.`, {
|
|
100301
|
-
retryable: true,
|
|
100302
|
-
reason: "PREPARATION_CONTENTION",
|
|
100303
|
-
expectedResponseVersion,
|
|
100304
|
-
responseVersion
|
|
100305
|
-
});
|
|
100306
|
-
}
|
|
100307
|
-
return AssessmentRuntimeError.from(assessmentResponseVersionConflict(expectedResponseVersion, responseVersion));
|
|
100308
|
-
}
|
|
100309
100261
|
function assessmentTrackableCourse(integration) {
|
|
100310
100262
|
if (!isTimebackGrade2(integration.grade) || !isTimebackSubject(integration.subject)) {
|
|
100311
100263
|
return null;
|
|
@@ -100371,7 +100323,7 @@ function buildConventionalAssessmentSnapshot(input) {
|
|
|
100371
100323
|
...input.activityData ? { activityData: input.activityData } : {},
|
|
100372
100324
|
assessment: assessmentPresentationForAttempt(input.assessment, input.attemptId),
|
|
100373
100325
|
responses: metadata2.responses,
|
|
100374
|
-
itemSubmissions: metadata2.itemSubmissions,
|
|
100326
|
+
itemSubmissions: assessmentFlowForPurpose(metadata2.purpose) === "item-submit" ? metadata2.itemSubmissions : [],
|
|
100375
100327
|
score: null,
|
|
100376
100328
|
selection
|
|
100377
100329
|
};
|
|
@@ -100386,13 +100338,6 @@ function buildAssessmentAwardRecord(input, mastery) {
|
|
|
100386
100338
|
...mastery.warnings ? { warnings: mastery.warnings } : {}
|
|
100387
100339
|
};
|
|
100388
100340
|
}
|
|
100389
|
-
function stageAssessmentAttemptSupersession(attempt) {
|
|
100390
|
-
Object.assign(attempt.result, ASSESSMENT_ATTEMPT_SUPERSEDED);
|
|
100391
|
-
Object.assign(attempt.selection, ASSESSMENT_ATTEMPT_SUPERSEDED);
|
|
100392
|
-
}
|
|
100393
|
-
function shouldConfirmAssessmentVersionUnavailable(details) {
|
|
100394
|
-
return typeof details === "object" && details !== null && "expectedRevision" in details && typeof details.expectedRevision === "string" && !("currentRevision" in details);
|
|
100395
|
-
}
|
|
100396
100341
|
function buildAssessmentAttemptSupersessionUpdate(result) {
|
|
100397
100342
|
if (result.status !== "active" || !isAssessmentAttemptOpen({
|
|
100398
100343
|
inProgress: result.inProgress ?? "",
|
|
@@ -100410,162 +100355,9 @@ function buildAssessmentAttemptSupersessionUpdate(result) {
|
|
|
100410
100355
|
metadata: result.metadata ?? {}
|
|
100411
100356
|
};
|
|
100412
100357
|
}
|
|
100413
|
-
async function resolveAvailableAssessmentAttempt(initialDecision, select3, resume) {
|
|
100414
|
-
let decision = initialDecision;
|
|
100415
|
-
while (decision?.kind === "resume") {
|
|
100416
|
-
const snapshot = await resume(decision);
|
|
100417
|
-
if (snapshot !== null) {
|
|
100418
|
-
return { kind: "resumed", snapshot };
|
|
100419
|
-
}
|
|
100420
|
-
decision = select3();
|
|
100421
|
-
}
|
|
100422
|
-
return { kind: "continue", decision };
|
|
100423
|
-
}
|
|
100424
|
-
function buildReviewItemResultMetadata(input) {
|
|
100425
|
-
const administration = itemAdministration(input.metadata.itemSubmissions, input.selection.itemIdentifier);
|
|
100426
|
-
if (!administration) {
|
|
100427
|
-
throw new Error(`Cannot create a review result for unadministered candidate ${input.selection.itemIdentifier}`);
|
|
100428
|
-
}
|
|
100429
|
-
return {
|
|
100430
|
-
version: PLAYCADEMY_ASSESSMENT_ITEM_RESULT_METADATA_VERSION,
|
|
100431
|
-
parentAttemptId: input.parentAttemptId,
|
|
100432
|
-
activityId: input.metadata.activityId,
|
|
100433
|
-
integrationId: input.metadata.integrationId,
|
|
100434
|
-
enrollmentId: input.metadata.enrollmentId,
|
|
100435
|
-
bankRevision: input.metadata.review.bankRevision,
|
|
100436
|
-
itemIdentifier: input.selection.itemIdentifier,
|
|
100437
|
-
standard: input.selection.standard,
|
|
100438
|
-
deliveredAt: administration.submittedAt,
|
|
100439
|
-
responseVersion: input.responseVersion ?? input.metadata.responseVersion,
|
|
100440
|
-
responses: input.metadata.responses[input.selection.itemIdentifier] ?? {},
|
|
100441
|
-
...input.submissionId ? { submissionId: input.submissionId } : {},
|
|
100442
|
-
...input.isCorrect === undefined ? {} : { isCorrect: input.isCorrect }
|
|
100443
|
-
};
|
|
100444
|
-
}
|
|
100445
|
-
function buildOpenReviewChildResult(input) {
|
|
100446
|
-
return {
|
|
100447
|
-
status: "active",
|
|
100448
|
-
assessmentLineItem: { sourcedId: input.childLineItemId },
|
|
100449
|
-
student: input.student,
|
|
100450
|
-
score: 0,
|
|
100451
|
-
scoreDate: input.metadata.deliveredAt,
|
|
100452
|
-
...ASSESSMENT_ATTEMPT_OPEN,
|
|
100453
|
-
metadata: {
|
|
100454
|
-
[PLAYCADEMY_ASSESSMENT_ITEM_RESULT_METADATA_KEY]: input.metadata
|
|
100455
|
-
}
|
|
100456
|
-
};
|
|
100457
|
-
}
|
|
100458
100358
|
function diagnosticQuestionLineItemId(parentLineItemId, itemIdentifier) {
|
|
100459
100359
|
return deterministicUUID(["playcademy:diagnostic-question-line-item:v1", parentLineItemId, itemIdentifier].join("\x00"));
|
|
100460
100360
|
}
|
|
100461
|
-
function diagnosticQuestionResultId(parentAttemptId, childLineItemId) {
|
|
100462
|
-
return deterministicUUID(["playcademy:diagnostic-question-result:v1", parentAttemptId, childLineItemId].join("\x00"));
|
|
100463
|
-
}
|
|
100464
|
-
function diagnosticAdministration(input) {
|
|
100465
|
-
const ledgerIndex = input.metadata.diagnostic.ledger.findIndex((entry) => entry.routingNodeKey === input.ledgerEntry.routingNodeKey);
|
|
100466
|
-
const persistedEntry = input.metadata.diagnostic.ledger[ledgerIndex];
|
|
100467
|
-
const administration = input.metadata.itemSubmissions[ledgerIndex];
|
|
100468
|
-
if (ledgerIndex === -1 || !persistedEntry || !administration || persistedEntry.stageKey !== input.ledgerEntry.stageKey || persistedEntry.trackKey !== input.ledgerEntry.trackKey || persistedEntry.itemIdentifier !== input.ledgerEntry.itemIdentifier || persistedEntry.isCorrect !== input.ledgerEntry.isCorrect || administration.itemIdentifier !== input.ledgerEntry.itemIdentifier || administration.isCorrect !== input.ledgerEntry.isCorrect) {
|
|
100469
|
-
throw new Error(`Cannot create a diagnostic result for uncommitted routing node ${input.ledgerEntry.routingNodeKey}`);
|
|
100470
|
-
}
|
|
100471
|
-
return administration;
|
|
100472
|
-
}
|
|
100473
|
-
function buildDiagnosticItemResultMetadata(input) {
|
|
100474
|
-
const administration = diagnosticAdministration(input);
|
|
100475
|
-
return {
|
|
100476
|
-
version: PLAYCADEMY_ASSESSMENT_ITEM_RESULT_METADATA_VERSION,
|
|
100477
|
-
parentAttemptId: input.parentAttemptId,
|
|
100478
|
-
activityId: input.metadata.activityId,
|
|
100479
|
-
integrationId: input.metadata.integrationId,
|
|
100480
|
-
enrollmentId: input.metadata.enrollmentId,
|
|
100481
|
-
definitionId: input.metadata.diagnostic.definitionId,
|
|
100482
|
-
diagnosticKey: input.metadata.diagnostic.diagnosticKey,
|
|
100483
|
-
routingRevision: input.metadata.diagnostic.routingRevision,
|
|
100484
|
-
contentRevision: input.metadata.selectedTest.contentRevision,
|
|
100485
|
-
stageKey: input.ledgerEntry.stageKey,
|
|
100486
|
-
trackKey: input.ledgerEntry.trackKey,
|
|
100487
|
-
routingNodeKey: input.ledgerEntry.routingNodeKey,
|
|
100488
|
-
itemIdentifier: input.ledgerEntry.itemIdentifier,
|
|
100489
|
-
deliveredAt: administration.submittedAt,
|
|
100490
|
-
responseVersion: administration.responseVersion,
|
|
100491
|
-
responses: input.metadata.responses[input.ledgerEntry.itemIdentifier] ?? {},
|
|
100492
|
-
submissionId: administration.submissionId,
|
|
100493
|
-
isCorrect: input.ledgerEntry.isCorrect
|
|
100494
|
-
};
|
|
100495
|
-
}
|
|
100496
|
-
function resolveDiagnosticChildResult(input) {
|
|
100497
|
-
if (!input.childResult) {
|
|
100498
|
-
return { action: "create" };
|
|
100499
|
-
}
|
|
100500
|
-
const childMetadata = playcademyDiagnosticAssessmentItemResultMetadata(input.childResult.metadata);
|
|
100501
|
-
const expectedMetadata = buildDiagnosticItemResultMetadata({
|
|
100502
|
-
metadata: input.parentMetadata,
|
|
100503
|
-
ledgerEntry: input.ledgerEntry,
|
|
100504
|
-
parentAttemptId: input.parentResult.sourcedId
|
|
100505
|
-
});
|
|
100506
|
-
if (input.childResult.assessmentLineItem.sourcedId !== input.childLineItemId || input.childResult.student.sourcedId !== input.parentResult.student.sourcedId || !childMetadata || canonicalJson2(childMetadata) !== canonicalJson2(expectedMetadata)) {
|
|
100507
|
-
return { action: "restore", reason: "incompatible" };
|
|
100508
|
-
}
|
|
100509
|
-
return input.childResult.status === "active" ? { action: "reuse", metadata: expectedMetadata } : { action: "restore", reason: "inactive" };
|
|
100510
|
-
}
|
|
100511
|
-
function buildFinalizedDiagnosticChildResult(input) {
|
|
100512
|
-
const administration = diagnosticAdministration(input);
|
|
100513
|
-
const metadata2 = buildDiagnosticItemResultMetadata(input);
|
|
100514
|
-
return {
|
|
100515
|
-
resultUpdate: {
|
|
100516
|
-
status: "active",
|
|
100517
|
-
assessmentLineItem: { sourcedId: input.childLineItemId },
|
|
100518
|
-
student: input.student,
|
|
100519
|
-
score: administration.score.earned,
|
|
100520
|
-
scoreDate: administration.submittedAt,
|
|
100521
|
-
...ASSESSMENT_ATTEMPT_COMPLETED,
|
|
100522
|
-
metadata: {
|
|
100523
|
-
[PLAYCADEMY_ASSESSMENT_ITEM_RESULT_METADATA_KEY]: metadata2
|
|
100524
|
-
}
|
|
100525
|
-
}
|
|
100526
|
-
};
|
|
100527
|
-
}
|
|
100528
|
-
function buildFinalizedReviewChildResult(input) {
|
|
100529
|
-
const metadata2 = buildReviewItemResultMetadata({
|
|
100530
|
-
metadata: input.metadata,
|
|
100531
|
-
selection: input.selection,
|
|
100532
|
-
parentAttemptId: input.parentAttemptId,
|
|
100533
|
-
submissionId: input.submissionId,
|
|
100534
|
-
isCorrect: input.itemResult.isCorrect
|
|
100535
|
-
});
|
|
100536
|
-
return {
|
|
100537
|
-
resultUpdate: {
|
|
100538
|
-
status: "active",
|
|
100539
|
-
assessmentLineItem: { sourcedId: input.childLineItemId },
|
|
100540
|
-
student: input.student,
|
|
100541
|
-
score: input.itemResult.score.earned,
|
|
100542
|
-
scoreDate: input.timestamp,
|
|
100543
|
-
...ASSESSMENT_ATTEMPT_COMPLETED,
|
|
100544
|
-
metadata: {
|
|
100545
|
-
[PLAYCADEMY_ASSESSMENT_ITEM_RESULT_METADATA_KEY]: metadata2
|
|
100546
|
-
}
|
|
100547
|
-
},
|
|
100548
|
-
outcome: {
|
|
100549
|
-
itemIdentifier: input.selection.itemIdentifier,
|
|
100550
|
-
standard: input.selection.standard,
|
|
100551
|
-
answered: Object.keys(input.metadata.responses[input.selection.itemIdentifier] ?? {}).length > 0,
|
|
100552
|
-
score: input.itemResult.score,
|
|
100553
|
-
isCorrect: input.itemResult.isCorrect
|
|
100554
|
-
}
|
|
100555
|
-
};
|
|
100556
|
-
}
|
|
100557
|
-
function resolveReviewChildResult(input) {
|
|
100558
|
-
if (!input.childResult) {
|
|
100559
|
-
return { action: "create" };
|
|
100560
|
-
}
|
|
100561
|
-
const childMetadata = playcademyAssessmentItemResultMetadata(input.childResult.metadata);
|
|
100562
|
-
const expectedResponses = input.parentMetadata.responses[input.selection.itemIdentifier] ?? {};
|
|
100563
|
-
const administration = itemAdministration(input.parentMetadata.itemSubmissions, input.selection.itemIdentifier);
|
|
100564
|
-
if (input.childResult.assessmentLineItem.sourcedId !== input.childLineItemId || input.childResult.student.sourcedId !== input.parentResult.student.sourcedId || !childMetadata || childMetadata.parentAttemptId !== input.parentResult.sourcedId || childMetadata.activityId !== input.parentMetadata.activityId || childMetadata.integrationId !== input.parentMetadata.integrationId || childMetadata.enrollmentId !== input.parentMetadata.enrollmentId || childMetadata.bankRevision !== input.parentMetadata.review.bankRevision || childMetadata.itemIdentifier !== input.selection.itemIdentifier || childMetadata.standard.framework !== input.selection.standard.framework || childMetadata.standard.identifier !== input.selection.standard.identifier || childMetadata.deliveredAt !== administration?.submittedAt || childMetadata.responseVersion > input.parentMetadata.responseVersion || canonicalJson2(childMetadata.responses) !== canonicalJson2(expectedResponses)) {
|
|
100565
|
-
return { action: "restore", reason: "incompatible" };
|
|
100566
|
-
}
|
|
100567
|
-
return input.childResult.status === "active" ? { action: "reuse", metadata: childMetadata } : { action: "restore", reason: "inactive" };
|
|
100568
|
-
}
|
|
100569
100361
|
function buildAssessmentSubmitResult(input) {
|
|
100570
100362
|
if (!input.metadata.score || !input.metadata.submissionId) {
|
|
100571
100363
|
return null;
|
|
@@ -100951,32 +100743,12 @@ function assessmentFixtureScoringKeys(assessment, questions) {
|
|
|
100951
100743
|
...Object.keys(responseAreas).length > 0 ? { responseAreas } : {}
|
|
100952
100744
|
};
|
|
100953
100745
|
}
|
|
100954
|
-
function prepareDiagnosticAssessmentResponses(assessment, current, input) {
|
|
100955
|
-
const update2 = { [input.itemIdentifier]: input.responses };
|
|
100956
|
-
validateAssessmentResponseUpdate(assessment, update2);
|
|
100957
|
-
const responses = applyAssessmentResponseUpdate(current, update2);
|
|
100958
|
-
const itemResponses = responses[input.itemIdentifier];
|
|
100959
|
-
validateAssessmentDraftResponses(assessment, itemResponses ? { [input.itemIdentifier]: itemResponses } : {});
|
|
100960
|
-
return responses;
|
|
100961
|
-
}
|
|
100962
100746
|
function validateAssessmentResponseUpdate(assessment, update2) {
|
|
100963
100747
|
const message = assessmentResponseUpdateValidationMessage(assessment, update2);
|
|
100964
100748
|
if (message) {
|
|
100965
100749
|
throw new AssessmentRuntimeError("INVALID_RESPONSE", message);
|
|
100966
100750
|
}
|
|
100967
100751
|
}
|
|
100968
|
-
function validateAssessmentResponses(assessment, responses) {
|
|
100969
|
-
const message = assessmentResponseValidationMessage(assessment, responses);
|
|
100970
|
-
if (message) {
|
|
100971
|
-
throw new AssessmentRuntimeError("INVALID_RESPONSE", message);
|
|
100972
|
-
}
|
|
100973
|
-
}
|
|
100974
|
-
function validateAssessmentDraftResponses(assessment, responses) {
|
|
100975
|
-
const message = assessmentDraftResponseValidationMessage(assessment, responses);
|
|
100976
|
-
if (message) {
|
|
100977
|
-
throw new AssessmentRuntimeError("INVALID_RESPONSE", message);
|
|
100978
|
-
}
|
|
100979
|
-
}
|
|
100980
100752
|
function assessmentScoringRequests(assessment, responses) {
|
|
100981
100753
|
return assessment.items.map((item) => ({
|
|
100982
100754
|
itemIdentifier: item.identifier,
|
|
@@ -101016,6 +100788,21 @@ function assessmentScoreFromPercentage(percentage, possible) {
|
|
|
101016
100788
|
const earned = Math.round(normalized * possible * 1e6) / 1e6;
|
|
101017
100789
|
return { earned, possible, normalized };
|
|
101018
100790
|
}
|
|
100791
|
+
function buildAssessmentResultCheckpoint(input) {
|
|
100792
|
+
return {
|
|
100793
|
+
status: input.result.status,
|
|
100794
|
+
assessmentLineItem: input.result.assessmentLineItem,
|
|
100795
|
+
student: input.result.student,
|
|
100796
|
+
score: input.result.score ?? 0,
|
|
100797
|
+
scoreDate: input.result.scoreDate,
|
|
100798
|
+
...ASSESSMENT_ATTEMPT_OPEN,
|
|
100799
|
+
metadata: mergeAssessmentRuntimeMetadata(input.result.metadata, {
|
|
100800
|
+
...input.metadata,
|
|
100801
|
+
transcript: input.transcript,
|
|
100802
|
+
updatedAt: input.timestamp
|
|
100803
|
+
})
|
|
100804
|
+
};
|
|
100805
|
+
}
|
|
101019
100806
|
function buildAssessmentResultSubmission(input) {
|
|
101020
100807
|
const metadata2 = {
|
|
101021
100808
|
...input.metadata,
|
|
@@ -102420,6 +102207,183 @@ var init_timeback_assessment_artifacts_util = __esm(() => {
|
|
|
102420
102207
|
init_timeback_review_mapping_util();
|
|
102421
102208
|
});
|
|
102422
102209
|
|
|
102210
|
+
// ../api-core/src/utils/timeback-assessment-scoring.util.ts
|
|
102211
|
+
async function scoreCachedAssessmentItems(input) {
|
|
102212
|
+
const selected = input.assessment.items.filter((item) => input.itemIdentifiers.has(item.identifier));
|
|
102213
|
+
const keys = new Map(await Promise.all(selected.map(async (item) => [
|
|
102214
|
+
item.identifier,
|
|
102215
|
+
await sha256Hex(canonicalJson2({
|
|
102216
|
+
identity: input.artifactIdentity,
|
|
102217
|
+
purpose: input.purpose,
|
|
102218
|
+
itemIdentifier: item.identifier,
|
|
102219
|
+
responses: input.responses[item.identifier] ?? {},
|
|
102220
|
+
strategy: input.artifact?.items.find((candidate) => candidate.itemIdentifier === item.identifier)?.strategy ?? "qti"
|
|
102221
|
+
}))
|
|
102222
|
+
])));
|
|
102223
|
+
const scored = new Map;
|
|
102224
|
+
for (const item of selected) {
|
|
102225
|
+
const cached3 = input.cache.get(keys.get(item.identifier));
|
|
102226
|
+
if (cached3) {
|
|
102227
|
+
scored.set(item.identifier, cached3);
|
|
102228
|
+
}
|
|
102229
|
+
}
|
|
102230
|
+
const missing = new Set(selected.filter((item) => !scored.has(item.identifier)).map((item) => item.identifier));
|
|
102231
|
+
const fresh = await scoreAssessmentItems({ ...input, itemIdentifiers: missing });
|
|
102232
|
+
for (const result of fresh) {
|
|
102233
|
+
input.cache.set(keys.get(result.itemIdentifier), result);
|
|
102234
|
+
scored.set(result.itemIdentifier, result);
|
|
102235
|
+
}
|
|
102236
|
+
return selected.map((item) => scored.get(item.identifier));
|
|
102237
|
+
}
|
|
102238
|
+
async function scoreAssessmentItems({
|
|
102239
|
+
assessment,
|
|
102240
|
+
responses,
|
|
102241
|
+
artifactIdentity,
|
|
102242
|
+
purpose,
|
|
102243
|
+
itemIdentifiers,
|
|
102244
|
+
artifact,
|
|
102245
|
+
processResponse,
|
|
102246
|
+
onEvent
|
|
102247
|
+
}) {
|
|
102248
|
+
const scoringItems = assessmentScoringRequests(assessment, responses).filter((item) => !itemIdentifiers || itemIdentifiers.has(item.itemIdentifier));
|
|
102249
|
+
const artifactItems = new Map(artifact?.items.map((item) => [item.itemIdentifier, item]));
|
|
102250
|
+
const scored = new Map;
|
|
102251
|
+
const qti2 = [];
|
|
102252
|
+
for (const item of scoringItems) {
|
|
102253
|
+
const artifactItem = artifactItems.get(item.itemIdentifier);
|
|
102254
|
+
let fallback2 = null;
|
|
102255
|
+
if (!artifact) {
|
|
102256
|
+
fallback2 = { reason: "artifact-unavailable" };
|
|
102257
|
+
} else if (!artifactItem) {
|
|
102258
|
+
fallback2 = { reason: "artifact-item-missing" };
|
|
102259
|
+
} else if (artifactItem.strategy === "qti") {
|
|
102260
|
+
fallback2 = { reason: "artifact-qti", artifactReason: artifactItem.reason };
|
|
102261
|
+
} else {
|
|
102262
|
+
const startedAt = Date.now();
|
|
102263
|
+
try {
|
|
102264
|
+
scored.set(item.itemIdentifier, {
|
|
102265
|
+
...scorePlatformAssessmentItem(artifactItem, responses[item.itemIdentifier]),
|
|
102266
|
+
grading: PLATFORM_CHILD_GRADING
|
|
102267
|
+
});
|
|
102268
|
+
onEvent("assessment.item_scoring", {
|
|
102269
|
+
"app.assessment.qti_item_identifier": item.itemIdentifier,
|
|
102270
|
+
"app.assessment.purpose": purpose,
|
|
102271
|
+
"app.assessment.artifact_identity_kind": artifactIdentity.kind,
|
|
102272
|
+
"app.assessment.grading_source": "platform-artifact",
|
|
102273
|
+
"app.assessment.grading_duration_ms": Date.now() - startedAt
|
|
102274
|
+
});
|
|
102275
|
+
} catch (error88) {
|
|
102276
|
+
fallback2 = { reason: "platform-grader-error", error: error88 };
|
|
102277
|
+
}
|
|
102278
|
+
}
|
|
102279
|
+
if (fallback2) {
|
|
102280
|
+
qti2.push({ item, ...fallback2 });
|
|
102281
|
+
}
|
|
102282
|
+
}
|
|
102283
|
+
if (qti2.length > 0) {
|
|
102284
|
+
const fallbackTasks = qti2.flatMap(({ item, reason, artifactReason, error: graderError }) => {
|
|
102285
|
+
const interactionResults = [];
|
|
102286
|
+
let startedAt;
|
|
102287
|
+
let completedInteractions = 0;
|
|
102288
|
+
let failed = false;
|
|
102289
|
+
const omittedResponseCount = item.requests.filter((request) => responses[item.itemIdentifier]?.[request.identifier] === undefined).length;
|
|
102290
|
+
let qtiRemoteInteractionCount = 0;
|
|
102291
|
+
let syntheticIncorrectInteractionCount = 0;
|
|
102292
|
+
function fallbackEvent(outcome) {
|
|
102293
|
+
onEvent("assessment.item_scoring_fallback", {
|
|
102294
|
+
"app.assessment.qti_item_identifier": item.itemIdentifier,
|
|
102295
|
+
"app.assessment.purpose": purpose,
|
|
102296
|
+
"app.assessment.artifact_identity_kind": artifactIdentity.kind,
|
|
102297
|
+
"app.assessment.grading_source": omittedResponseCount > 0 ? "platform-qti-adapter" : "timeback-qti",
|
|
102298
|
+
"app.assessment.fallback_reason": reason,
|
|
102299
|
+
"app.assessment.fallback_duration_ms": startedAt === undefined ? 0 : Date.now() - startedAt,
|
|
102300
|
+
"app.assessment.qti_remote_interaction_count": qtiRemoteInteractionCount,
|
|
102301
|
+
"app.assessment.synthetic_incorrect_interaction_count": syntheticIncorrectInteractionCount,
|
|
102302
|
+
...artifactReason ? { "app.assessment.artifact_fallback_reason": artifactReason } : {},
|
|
102303
|
+
...graderError ? {
|
|
102304
|
+
"app.assessment.platform_grader_exception_type": errorType(graderError)
|
|
102305
|
+
} : {},
|
|
102306
|
+
...outcome
|
|
102307
|
+
});
|
|
102308
|
+
}
|
|
102309
|
+
function completeItem() {
|
|
102310
|
+
const earned = assessmentItemEarnedScore(interactionResults.map((result) => Number.isFinite(result.score) ? result.score : 0), item.maxScore);
|
|
102311
|
+
const possible = item.maxScore;
|
|
102312
|
+
fallbackEvent({ "app.assessment.fallback_outcome": "succeeded" });
|
|
102313
|
+
scored.set(item.itemIdentifier, {
|
|
102314
|
+
itemIdentifier: item.itemIdentifier,
|
|
102315
|
+
score: {
|
|
102316
|
+
earned,
|
|
102317
|
+
possible,
|
|
102318
|
+
normalized: possible === 0 ? 0 : earned / possible
|
|
102319
|
+
},
|
|
102320
|
+
isCorrect: assessmentItemCorrectness(interactionResults.map((result) => result.isCorrect)),
|
|
102321
|
+
grading: omittedResponseCount > 0 ? PLATFORM_QTI_ADAPTER_CHILD_GRADING : QTI_CHILD_GRADING
|
|
102322
|
+
});
|
|
102323
|
+
}
|
|
102324
|
+
if (item.requests.length === 0) {
|
|
102325
|
+
completeItem();
|
|
102326
|
+
return [];
|
|
102327
|
+
}
|
|
102328
|
+
return item.requests.map((request, index2) => async () => {
|
|
102329
|
+
startedAt ??= Date.now();
|
|
102330
|
+
try {
|
|
102331
|
+
const response = responses[item.itemIdentifier]?.[request.identifier];
|
|
102332
|
+
if (response === undefined) {
|
|
102333
|
+
syntheticIncorrectInteractionCount += 1;
|
|
102334
|
+
interactionResults[index2] = { score: 0, isCorrect: false };
|
|
102335
|
+
} else {
|
|
102336
|
+
qtiRemoteInteractionCount += 1;
|
|
102337
|
+
interactionResults[index2] = await processResponse(item.itemIdentifier, request);
|
|
102338
|
+
}
|
|
102339
|
+
completedInteractions += 1;
|
|
102340
|
+
if (completedInteractions === item.requests.length && !failed) {
|
|
102341
|
+
completeItem();
|
|
102342
|
+
}
|
|
102343
|
+
} catch (error88) {
|
|
102344
|
+
if (!failed) {
|
|
102345
|
+
failed = true;
|
|
102346
|
+
fallbackEvent({
|
|
102347
|
+
"app.assessment.fallback_outcome": "failed",
|
|
102348
|
+
"exception.type": errorType(error88),
|
|
102349
|
+
"app.error.message": errorMessage(error88)
|
|
102350
|
+
});
|
|
102351
|
+
}
|
|
102352
|
+
throw error88;
|
|
102353
|
+
}
|
|
102354
|
+
});
|
|
102355
|
+
});
|
|
102356
|
+
await runWithConcurrency(fallbackTasks, SCORING_CONCURRENCY, (task) => task());
|
|
102357
|
+
}
|
|
102358
|
+
return scoringItems.map((item) => {
|
|
102359
|
+
const result = scored.get(item.itemIdentifier);
|
|
102360
|
+
if (!result) {
|
|
102361
|
+
throw new Error(`Assessment item ${item.itemIdentifier} was not scored`);
|
|
102362
|
+
}
|
|
102363
|
+
return result;
|
|
102364
|
+
});
|
|
102365
|
+
}
|
|
102366
|
+
var QTI_CHILD_GRADING, PLATFORM_QTI_ADAPTER_CHILD_GRADING, PLATFORM_CHILD_GRADING, SCORING_CONCURRENCY = 4;
|
|
102367
|
+
var init_timeback_assessment_scoring_util = __esm(() => {
|
|
102368
|
+
init_assessment_runtime2();
|
|
102369
|
+
init_assessment_artifact_envelope_util();
|
|
102370
|
+
init_timeback_assessment_runtime_util();
|
|
102371
|
+
QTI_CHILD_GRADING = {
|
|
102372
|
+
source: "timeback-qti",
|
|
102373
|
+
graderVersion: "timeback-qti-process-response-v1"
|
|
102374
|
+
};
|
|
102375
|
+
PLATFORM_QTI_ADAPTER_CHILD_GRADING = {
|
|
102376
|
+
source: "platform-qti-adapter",
|
|
102377
|
+
graderVersion: "platform-qti-adapter-v1",
|
|
102378
|
+
qtiGraderVersion: "timeback-qti-process-response-v1"
|
|
102379
|
+
};
|
|
102380
|
+
PLATFORM_CHILD_GRADING = {
|
|
102381
|
+
source: "platform-artifact",
|
|
102382
|
+
artifactVersion: `assessment-artifact-v${ASSESSMENT_ARTIFACT_SCHEMA_VERSION}`,
|
|
102383
|
+
graderVersion: `platform-grader-v${ASSESSMENT_GRADER_PROTOCOL_VERSION}`
|
|
102384
|
+
};
|
|
102385
|
+
});
|
|
102386
|
+
|
|
102423
102387
|
// ../api-core/src/utils/timeback-assessment-child-result.util.ts
|
|
102424
102388
|
async function buildSubmissionChildResultCommand(input) {
|
|
102425
102389
|
const { submission, context: context2 } = input;
|
|
@@ -102435,12 +102399,9 @@ async function buildSubmissionChildResultCommand(input) {
|
|
|
102435
102399
|
score: submission.score,
|
|
102436
102400
|
grading: input.grading
|
|
102437
102401
|
};
|
|
102438
|
-
if (context2.kind
|
|
102439
|
-
|
|
102440
|
-
|
|
102441
|
-
...typeof isCorrect === "boolean" ? { isCorrect } : {},
|
|
102442
|
-
context: context2
|
|
102443
|
-
}, submission.submittedAt);
|
|
102402
|
+
if (context2.kind !== "platform-routed-diagnostic") {
|
|
102403
|
+
const payload = { ...common2, ...typeof isCorrect === "boolean" ? { isCorrect } : {} };
|
|
102404
|
+
return context2.kind === "review" ? buildAssessmentChildResultCommand({ ...payload, context: context2 }, submission.submittedAt) : buildAssessmentChildResultCommand({ ...payload, context: context2 }, submission.submittedAt);
|
|
102444
102405
|
}
|
|
102445
102406
|
if (typeof isCorrect !== "boolean") {
|
|
102446
102407
|
throw new Error("Diagnostic child results require determinate correctness");
|
|
@@ -102461,11 +102422,7 @@ function assessmentChildLineItemMetadataMatches(key, stored, expected) {
|
|
|
102461
102422
|
if (!Array.isArray(storedStandards) || !Array.isArray(expectedStandards)) {
|
|
102462
102423
|
return false;
|
|
102463
102424
|
}
|
|
102464
|
-
|
|
102465
|
-
function contains(standards, candidate) {
|
|
102466
|
-
return standards.some((standard) => canonicalJson2(standard) === canonicalJson2(candidate));
|
|
102467
|
-
}
|
|
102468
|
-
return sameDefinition && (expectedStandards.every((standard) => contains(storedStandards, standard)) || storedStandards.every((standard) => contains(expectedStandards, standard)));
|
|
102425
|
+
return canonicalJson2(reviewDefinitionWithoutStandards(storedDefinition)) === canonicalJson2(reviewDefinitionWithoutStandards(expectedDefinition));
|
|
102469
102426
|
}
|
|
102470
102427
|
function assessmentChildLineItemMatches(stored, expected) {
|
|
102471
102428
|
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) {
|
|
@@ -102523,19 +102480,14 @@ async function classifyAssessmentChildResult(input) {
|
|
|
102523
102480
|
result.scoreScale,
|
|
102524
102481
|
result.scorePercentile,
|
|
102525
102482
|
result.comment,
|
|
102526
|
-
result.learningObjectiveSet
|
|
102527
|
-
result.incomplete,
|
|
102528
|
-
result.late,
|
|
102529
|
-
result.missing
|
|
102483
|
+
result.learningObjectiveSet
|
|
102530
102484
|
];
|
|
102531
|
-
|
|
102485
|
+
const flags2 = [result.incomplete, result.late, result.missing];
|
|
102486
|
+
if (auxiliaryFields.some((value) => value !== undefined && value !== null) || flags2.some((value) => value !== undefined && value !== null && value !== "false")) {
|
|
102532
102487
|
return { outcome: "conflict", reason: "auxiliary-field-mismatch" };
|
|
102533
102488
|
}
|
|
102534
102489
|
return { outcome: "replay" };
|
|
102535
102490
|
}
|
|
102536
|
-
function hasApiStatus(error88, statusCode) {
|
|
102537
|
-
return isApiError(error88) && error88.statusCode === statusCode;
|
|
102538
|
-
}
|
|
102539
102491
|
async function persistAssessmentChildResult(input) {
|
|
102540
102492
|
const { assessmentResults, childLineItemId } = input;
|
|
102541
102493
|
const verification2 = await verifyAssessmentChildResultCommand(input.command);
|
|
@@ -102544,38 +102496,127 @@ async function persistAssessmentChildResult(input) {
|
|
|
102544
102496
|
}
|
|
102545
102497
|
const { command } = verification2;
|
|
102546
102498
|
const resultId = await assessmentChildResultId(command.payload.attempt.attemptId, command.payload.administration.submissionId);
|
|
102547
|
-
|
|
102548
|
-
|
|
102549
|
-
|
|
102499
|
+
await assessmentResults.upsert(resultId, buildAssessmentChildResultUpdate(command, childLineItemId));
|
|
102500
|
+
}
|
|
102501
|
+
function assessmentTranscriptChildLineItemId(context2, itemIdentifier) {
|
|
102502
|
+
return context2.metadata.purpose === "review" ? reviewQuestionLineItemId(context2.parentLineItemId, itemIdentifier) : diagnosticQuestionLineItemId(context2.parentLineItemId, itemIdentifier);
|
|
102503
|
+
}
|
|
102504
|
+
async function reconcileAssessmentTranscriptChildren({
|
|
102505
|
+
context: context2,
|
|
102506
|
+
graded,
|
|
102507
|
+
only,
|
|
102508
|
+
assessmentResults,
|
|
102509
|
+
ensureLineItems,
|
|
102510
|
+
onPersisted
|
|
102511
|
+
}) {
|
|
102512
|
+
const metadata2 = graded.metadata;
|
|
102513
|
+
const assessmentKey = metadata2.selectedTest.assessmentKey;
|
|
102514
|
+
if (!assessmentKey) {
|
|
102515
|
+
throw new Error("Assessment context requires a selected assessment key");
|
|
102516
|
+
}
|
|
102517
|
+
const submissions = metadata2.itemSubmissions.filter((submission) => !only || only.has(submission.submissionId));
|
|
102518
|
+
const administered = new Set(submissions.map((submission) => submission.itemIdentifier));
|
|
102519
|
+
await ensureLineItems({
|
|
102520
|
+
items: graded.assessment.items.filter((item) => administered.has(item.identifier))
|
|
102521
|
+
});
|
|
102522
|
+
await runWithConcurrency(submissions, 4, async (submission) => {
|
|
102523
|
+
const index2 = submission.responseVersion - 1;
|
|
102524
|
+
let flow;
|
|
102525
|
+
if (metadata2.purpose === "review") {
|
|
102526
|
+
const selection = metadata2.review.selections.find((item) => item.itemIdentifier === submission.itemIdentifier);
|
|
102527
|
+
flow = {
|
|
102528
|
+
kind: "review",
|
|
102529
|
+
bankRevision: metadata2.review.bankRevision,
|
|
102530
|
+
standard: selection.standard
|
|
102531
|
+
};
|
|
102532
|
+
} else if (metadata2.purpose === "diagnostic" && metadata2.diagnostic) {
|
|
102533
|
+
flow = {
|
|
102534
|
+
kind: "platform-routed-diagnostic",
|
|
102535
|
+
definitionId: metadata2.diagnostic.definitionId,
|
|
102536
|
+
diagnosticKey: metadata2.diagnostic.diagnosticKey,
|
|
102537
|
+
routingRevision: metadata2.diagnostic.routingRevision,
|
|
102538
|
+
transition: metadata2.diagnostic.ledger[index2]
|
|
102539
|
+
};
|
|
102540
|
+
} else {
|
|
102541
|
+
flow = { kind: "fixed", purpose: metadata2.purpose };
|
|
102542
|
+
}
|
|
102543
|
+
const command = await buildSubmissionChildResultCommand({
|
|
102544
|
+
attempt: {
|
|
102545
|
+
attemptId: context2.attemptId,
|
|
102546
|
+
gameId: context2.gameId,
|
|
102547
|
+
studentId: context2.studentId,
|
|
102548
|
+
activityId: metadata2.activityId,
|
|
102549
|
+
courseId: metadata2.courseId,
|
|
102550
|
+
integrationId: metadata2.integrationId,
|
|
102551
|
+
enrollmentId: metadata2.enrollmentId,
|
|
102552
|
+
selectedTest: { ...metadata2.selectedTest, assessmentKey }
|
|
102553
|
+
},
|
|
102554
|
+
submission,
|
|
102555
|
+
responses: graded.transcript.administrations[index2].responses,
|
|
102556
|
+
grading: submission.grading ?? QTI_CHILD_GRADING,
|
|
102557
|
+
context: flow
|
|
102558
|
+
});
|
|
102559
|
+
await persistAssessmentChildResult({
|
|
102560
|
+
assessmentResults,
|
|
102550
102561
|
command,
|
|
102551
|
-
childLineItemId
|
|
102562
|
+
childLineItemId: await assessmentTranscriptChildLineItemId(context2, submission.itemIdentifier)
|
|
102552
102563
|
});
|
|
102553
|
-
|
|
102564
|
+
onPersisted?.(submission.submissionId);
|
|
102565
|
+
});
|
|
102566
|
+
}
|
|
102567
|
+
async function recoveredAssessmentAdministration(context2, result) {
|
|
102568
|
+
const verified = await verifyAssessmentChildResultCommand(result.metadata);
|
|
102569
|
+
if (!verified.ok) {
|
|
102570
|
+
return null;
|
|
102554
102571
|
}
|
|
102555
|
-
|
|
102556
|
-
|
|
102557
|
-
|
|
102558
|
-
|
|
102559
|
-
|
|
102572
|
+
const { command } = verified;
|
|
102573
|
+
const { attempt, administration, responses, context: flow } = command.payload;
|
|
102574
|
+
if (attempt.attemptId !== context2.attemptId || attempt.gameId !== context2.gameId || attempt.studentId !== context2.studentId || attempt.integrationId !== context2.metadata.integrationId || attempt.enrollmentId !== context2.metadata.enrollmentId || attempt.courseId !== context2.metadata.courseId || attempt.activityId !== context2.metadata.activityId || canonicalJson2(attempt.selectedTest) !== canonicalJson2(context2.metadata.selectedTest) || result.sourcedId !== await assessmentChildResultId(context2.attemptId, administration.submissionId)) {
|
|
102575
|
+
return null;
|
|
102576
|
+
}
|
|
102577
|
+
const disposition = await classifyAssessmentChildResult({
|
|
102578
|
+
result,
|
|
102579
|
+
command,
|
|
102580
|
+
childLineItemId: await assessmentTranscriptChildLineItemId(context2, administration.itemIdentifier)
|
|
102581
|
+
});
|
|
102582
|
+
if (disposition.outcome !== "replay") {
|
|
102583
|
+
return null;
|
|
102584
|
+
}
|
|
102585
|
+
return {
|
|
102586
|
+
sequence: administration.responseVersion,
|
|
102587
|
+
entry: {
|
|
102588
|
+
submissionId: administration.submissionId,
|
|
102589
|
+
itemIdentifier: administration.itemIdentifier,
|
|
102590
|
+
responses,
|
|
102591
|
+
...flow.kind === "platform-routed-diagnostic" ? { routingNodeKey: flow.transition.routingNodeKey } : {}
|
|
102592
|
+
}
|
|
102593
|
+
};
|
|
102594
|
+
}
|
|
102595
|
+
async function recoverAssessmentTranscript(context2, results) {
|
|
102596
|
+
const entries = new Map;
|
|
102597
|
+
for (const result of results) {
|
|
102598
|
+
const recovered = await recoveredAssessmentAdministration(context2, result);
|
|
102599
|
+
if (recovered) {
|
|
102600
|
+
const atPosition = entries.get(recovered.sequence) ?? new Map;
|
|
102601
|
+
atPosition.set(recovered.entry.submissionId, recovered.entry);
|
|
102602
|
+
entries.set(recovered.sequence, atPosition);
|
|
102560
102603
|
}
|
|
102561
102604
|
}
|
|
102562
|
-
|
|
102563
|
-
|
|
102564
|
-
|
|
102565
|
-
|
|
102566
|
-
|
|
102567
|
-
return { outcome: "created", resultId };
|
|
102568
|
-
} catch (error88) {
|
|
102569
|
-
if (!hasApiStatus(error88, 409)) {
|
|
102570
|
-
throw error88;
|
|
102605
|
+
const transcript = emptyAssessmentTranscript(context2.attemptId, context2.metadata.selectedTest.contentRevision);
|
|
102606
|
+
for (let sequence2 = 1;sequence2 <= entries.size; sequence2 += 1) {
|
|
102607
|
+
const candidates = entries.get(sequence2);
|
|
102608
|
+
if (!candidates || candidates.size !== 1) {
|
|
102609
|
+
break;
|
|
102571
102610
|
}
|
|
102611
|
+
transcript.administrations.push(candidates.values().next().value);
|
|
102572
102612
|
}
|
|
102573
|
-
return
|
|
102613
|
+
return transcript;
|
|
102574
102614
|
}
|
|
102575
|
-
var init_timeback_assessment_child_result_util = __esm(
|
|
102615
|
+
var init_timeback_assessment_child_result_util = __esm(() => {
|
|
102576
102616
|
init_src();
|
|
102577
102617
|
init_assessment_runtime2();
|
|
102578
|
-
|
|
102618
|
+
init_timeback_assessment_runtime_util();
|
|
102619
|
+
init_timeback_assessment_scoring_util();
|
|
102579
102620
|
});
|
|
102580
102621
|
|
|
102581
102622
|
// ../api-core/src/utils/timeback-assessment-preparation.util.ts
|
|
@@ -102614,21 +102655,6 @@ function assessmentPreparationFlightKey(input) {
|
|
|
102614
102655
|
async function assessmentPreparationReceiptMatches(plan, request) {
|
|
102615
102656
|
return plan.gameId === request.gameId && plan.studentId === request.studentId && plan.activityId === request.input.activityId && plan.purpose === request.input.purpose && plan.requestHash === await assessmentPreparationRequestHash(request.input);
|
|
102616
102657
|
}
|
|
102617
|
-
function resolveAssessmentPreparationSelection(decision, candidateCourseCount) {
|
|
102618
|
-
if (decision?.kind === "resume") {
|
|
102619
|
-
return {
|
|
102620
|
-
status: "blocked",
|
|
102621
|
-
result: { status: "canonical_required", reason: "resumable_attempt" }
|
|
102622
|
-
};
|
|
102623
|
-
}
|
|
102624
|
-
if (candidateCourseCount !== 1) {
|
|
102625
|
-
return {
|
|
102626
|
-
status: "blocked",
|
|
102627
|
-
result: { status: "unprepared", reason: "prerequisite_unavailable" }
|
|
102628
|
-
};
|
|
102629
|
-
}
|
|
102630
|
-
return decision ? { status: "ready", decision } : { status: "unavailable" };
|
|
102631
|
-
}
|
|
102632
102658
|
function preparedAssessmentIntegration(plan, candidates) {
|
|
102633
102659
|
if (candidates.rows.length !== 1) {
|
|
102634
102660
|
return null;
|
|
@@ -102680,6 +102706,7 @@ function buildPreparedAssessmentMetadata(plan, timestamp6, testName) {
|
|
|
102680
102706
|
version: PLAYCADEMY_ASSESSMENT_RESULT_METADATA_VERSION,
|
|
102681
102707
|
activityId: plan.activityId,
|
|
102682
102708
|
courseId: plan.courseId,
|
|
102709
|
+
courseName: plan.courseName,
|
|
102683
102710
|
integrationId: plan.integrationId,
|
|
102684
102711
|
enrollmentId: plan.enrollmentId,
|
|
102685
102712
|
selectedTest: plan.selectedTest,
|
|
@@ -102710,7 +102737,6 @@ function buildPreparedAssessmentMetadata(plan, timestamp6, testName) {
|
|
|
102710
102737
|
testName,
|
|
102711
102738
|
routingRevision: plan.diagnostic.routingRevision,
|
|
102712
102739
|
ledger: [],
|
|
102713
|
-
routingSnapshots: [],
|
|
102714
102740
|
state: plan.diagnostic.initialState
|
|
102715
102741
|
}
|
|
102716
102742
|
};
|
|
@@ -103005,127 +103031,6 @@ var init_timeback_assessment_publication_util = __esm(() => {
|
|
|
103005
103031
|
MANAGED_ASSET_URL = new RegExp(`https?://[^\\s"')]+/(${ASSESSMENT_ASSET_KEY_PREFIX}v1/sha256/[a-f0-9]{64}\\.[a-z0-9]+)`, "gi");
|
|
103006
103032
|
});
|
|
103007
103033
|
|
|
103008
|
-
// ../api-core/src/utils/timeback-assessment-reconciliation.util.ts
|
|
103009
|
-
function assessmentChildProjectionIsTerminal(result) {
|
|
103010
|
-
const state = { inProgress: result.inProgress ?? "", scoreStatus: result.scoreStatus };
|
|
103011
|
-
return isAssessmentAttemptAwaitingAward(state) || isAssessmentAttemptCompleted(state);
|
|
103012
|
-
}
|
|
103013
|
-
async function reconcileReviewChildResults({
|
|
103014
|
-
initial,
|
|
103015
|
-
writeOpen,
|
|
103016
|
-
crossBarrier,
|
|
103017
|
-
readLatest,
|
|
103018
|
-
restoreTerminal
|
|
103019
|
-
}) {
|
|
103020
|
-
if (initial.metadata.purpose !== "review") {
|
|
103021
|
-
throw new Error("A standards-review request selected a non-review attempt");
|
|
103022
|
-
}
|
|
103023
|
-
if (assessmentChildProjectionIsTerminal(initial.result)) {
|
|
103024
|
-
const settled = { result: initial.result, metadata: initial.metadata };
|
|
103025
|
-
await restoreTerminal(settled);
|
|
103026
|
-
return settled;
|
|
103027
|
-
}
|
|
103028
|
-
await writeOpen();
|
|
103029
|
-
await crossBarrier();
|
|
103030
|
-
const current = await readLatest();
|
|
103031
|
-
if (current.metadata.purpose !== "review") {
|
|
103032
|
-
throw new Error("A standards-review request selected a non-review attempt");
|
|
103033
|
-
}
|
|
103034
|
-
const reviewed = { result: current.result, metadata: current.metadata };
|
|
103035
|
-
if (assessmentChildProjectionIsTerminal(current.result)) {
|
|
103036
|
-
await restoreTerminal(reviewed);
|
|
103037
|
-
}
|
|
103038
|
-
return reviewed;
|
|
103039
|
-
}
|
|
103040
|
-
var init_timeback_assessment_reconciliation_util = __esm(() => {
|
|
103041
|
-
init_assessment_runtime2();
|
|
103042
|
-
});
|
|
103043
|
-
|
|
103044
|
-
// ../api-core/src/utils/timeback-assessment-replay.util.ts
|
|
103045
|
-
function resolveDiagnosticItemReplay(metadata2, input) {
|
|
103046
|
-
const priorIndex = metadata2.itemSubmissions.findIndex((submission) => submission.submissionId === input.submissionId);
|
|
103047
|
-
if (priorIndex === -1) {
|
|
103048
|
-
return { action: "missing" };
|
|
103049
|
-
}
|
|
103050
|
-
const prior = metadata2.itemSubmissions[priorIndex];
|
|
103051
|
-
const ledgerEntry = metadata2.diagnostic.ledger[priorIndex];
|
|
103052
|
-
if (!prior || !ledgerEntry || prior.itemIdentifier !== input.itemIdentifier || ledgerEntry.routingNodeKey !== input.routingNodeKey || !itemResponseUpdateMatches(metadata2.responses[input.itemIdentifier], input.responses)) {
|
|
103053
|
-
return {
|
|
103054
|
-
action: "conflict",
|
|
103055
|
-
failure: assessmentFlowViolation("This diagnostic submission ID was already used for a different request.", {
|
|
103056
|
-
submissionId: input.submissionId,
|
|
103057
|
-
routingNodeKey: input.routingNodeKey,
|
|
103058
|
-
itemIdentifier: input.itemIdentifier
|
|
103059
|
-
})
|
|
103060
|
-
};
|
|
103061
|
-
}
|
|
103062
|
-
return {
|
|
103063
|
-
action: "replay",
|
|
103064
|
-
submission: prior,
|
|
103065
|
-
ledgerEntry,
|
|
103066
|
-
priorIndex,
|
|
103067
|
-
routing: metadata2.diagnostic.routingSnapshots?.[priorIndex]
|
|
103068
|
-
};
|
|
103069
|
-
}
|
|
103070
|
-
function buildAssessmentItemReplayResult(attemptId, metadata2, submission) {
|
|
103071
|
-
return {
|
|
103072
|
-
attemptId,
|
|
103073
|
-
responseVersion: metadata2.responseVersion,
|
|
103074
|
-
status: "in_progress",
|
|
103075
|
-
responses: metadata2.responses,
|
|
103076
|
-
itemSubmissions: metadata2.itemSubmissions,
|
|
103077
|
-
submission
|
|
103078
|
-
};
|
|
103079
|
-
}
|
|
103080
|
-
function diagnosticReceipt(submission, ledgerEntry) {
|
|
103081
|
-
return {
|
|
103082
|
-
submissionId: submission.submissionId,
|
|
103083
|
-
routingNodeKey: ledgerEntry.routingNodeKey,
|
|
103084
|
-
itemIdentifier: submission.itemIdentifier,
|
|
103085
|
-
submittedAt: submission.submittedAt,
|
|
103086
|
-
responseVersion: submission.responseVersion,
|
|
103087
|
-
answered: submission.answered,
|
|
103088
|
-
score: submission.score,
|
|
103089
|
-
isCorrect: ledgerEntry.isCorrect
|
|
103090
|
-
};
|
|
103091
|
-
}
|
|
103092
|
-
function buildDiagnosticItemReplayResult(attemptId, submission, ledgerEntry, routing) {
|
|
103093
|
-
return {
|
|
103094
|
-
attemptId,
|
|
103095
|
-
responseVersion: submission.responseVersion,
|
|
103096
|
-
status: "in_progress",
|
|
103097
|
-
routing,
|
|
103098
|
-
submission: diagnosticReceipt(submission, ledgerEntry)
|
|
103099
|
-
};
|
|
103100
|
-
}
|
|
103101
|
-
function requireAssessmentMutationAccess(attemptId, integrationStatus, enrollment) {
|
|
103102
|
-
if (!enrollment || integrationStatus === "deactivated") {
|
|
103103
|
-
throw AssessmentRuntimeError.from(assessmentAttemptUnauthorized(attemptId));
|
|
103104
|
-
}
|
|
103105
|
-
return enrollment;
|
|
103106
|
-
}
|
|
103107
|
-
function committedReviewItemResults(metadata2, assessment) {
|
|
103108
|
-
const available = new Set(assessment.items.map((item) => item.identifier));
|
|
103109
|
-
const seen = new Set;
|
|
103110
|
-
return metadata2.itemSubmissions.map((submission) => {
|
|
103111
|
-
if (!available.has(submission.itemIdentifier) || seen.has(submission.itemIdentifier)) {
|
|
103112
|
-
throw AssessmentRuntimeError.from(assessmentFlowViolation("The persisted review item-submission ledger is corrupt.", {
|
|
103113
|
-
itemIdentifier: submission.itemIdentifier
|
|
103114
|
-
}));
|
|
103115
|
-
}
|
|
103116
|
-
seen.add(submission.itemIdentifier);
|
|
103117
|
-
return {
|
|
103118
|
-
itemIdentifier: submission.itemIdentifier,
|
|
103119
|
-
score: submission.score,
|
|
103120
|
-
isCorrect: submission.isCorrect
|
|
103121
|
-
};
|
|
103122
|
-
});
|
|
103123
|
-
}
|
|
103124
|
-
var init_timeback_assessment_replay_util = __esm(() => {
|
|
103125
|
-
init_assessment_runtime2();
|
|
103126
|
-
init_errors2();
|
|
103127
|
-
});
|
|
103128
|
-
|
|
103129
103034
|
// ../api-core/src/utils/timeback-assessment-routing.util.ts
|
|
103130
103035
|
function diagnosticRoutingFallbackReason(artifact, routingRevision) {
|
|
103131
103036
|
if (!artifact) {
|
|
@@ -103275,154 +103180,256 @@ var init_timeback_assessment_rules_util = __esm(() => {
|
|
|
103275
103180
|
init_errors2();
|
|
103276
103181
|
});
|
|
103277
103182
|
|
|
103278
|
-
// ../api-core/src/utils/timeback-assessment-
|
|
103279
|
-
|
|
103280
|
-
|
|
103281
|
-
|
|
103282
|
-
|
|
103283
|
-
|
|
103284
|
-
|
|
103285
|
-
|
|
103286
|
-
|
|
103287
|
-
|
|
103288
|
-
|
|
103289
|
-
|
|
103290
|
-
|
|
103291
|
-
|
|
103292
|
-
|
|
103293
|
-
|
|
103294
|
-
|
|
103295
|
-
|
|
103296
|
-
|
|
103297
|
-
|
|
103298
|
-
|
|
103299
|
-
|
|
103300
|
-
|
|
103301
|
-
|
|
103302
|
-
|
|
103303
|
-
|
|
103304
|
-
|
|
103305
|
-
|
|
103306
|
-
|
|
103307
|
-
|
|
103308
|
-
|
|
103309
|
-
|
|
103310
|
-
|
|
103311
|
-
|
|
103312
|
-
|
|
103313
|
-
|
|
103314
|
-
|
|
103315
|
-
|
|
103316
|
-
|
|
103317
|
-
|
|
103183
|
+
// ../api-core/src/utils/timeback-assessment-replay.util.ts
|
|
103184
|
+
function diagnosticReceipt(submission, ledgerEntry) {
|
|
103185
|
+
return {
|
|
103186
|
+
submissionId: submission.submissionId,
|
|
103187
|
+
routingNodeKey: ledgerEntry.routingNodeKey,
|
|
103188
|
+
itemIdentifier: submission.itemIdentifier,
|
|
103189
|
+
submittedAt: submission.submittedAt,
|
|
103190
|
+
responseVersion: submission.responseVersion,
|
|
103191
|
+
answered: submission.answered,
|
|
103192
|
+
score: submission.score,
|
|
103193
|
+
isCorrect: ledgerEntry.isCorrect
|
|
103194
|
+
};
|
|
103195
|
+
}
|
|
103196
|
+
|
|
103197
|
+
// ../api-core/src/utils/timeback-assessment-transcript.util.ts
|
|
103198
|
+
function parseAssessmentTranscript(value) {
|
|
103199
|
+
const parsed = AssessmentTranscriptSchema.safeParse(value);
|
|
103200
|
+
if (!parsed.success) {
|
|
103201
|
+
throw new ValidationError("Invalid assessment transcript");
|
|
103202
|
+
}
|
|
103203
|
+
return parsed.data;
|
|
103204
|
+
}
|
|
103205
|
+
function assessmentTokenMetadata(metadata2) {
|
|
103206
|
+
const context2 = { ...metadata2, updatedAt: metadata2.startedAt };
|
|
103207
|
+
delete context2.transcript;
|
|
103208
|
+
return context2;
|
|
103209
|
+
}
|
|
103210
|
+
function requireAssessmentTokenContext(context2, scope) {
|
|
103211
|
+
if (!context2 || context2.gameId !== scope.gameId || context2.studentId !== scope.studentId || context2.attemptId !== scope.attemptId) {
|
|
103212
|
+
throw AssessmentRuntimeError.from(assessmentAttemptUnauthorized(scope.attemptId));
|
|
103213
|
+
}
|
|
103214
|
+
return context2;
|
|
103215
|
+
}
|
|
103216
|
+
function requireAssessmentFeedbackContext(tokenContext, scope) {
|
|
103217
|
+
const context2 = requireAssessmentTokenContext(tokenContext, scope);
|
|
103218
|
+
const metadata2 = context2.metadata;
|
|
103219
|
+
if (assessmentFlowForPurpose(metadata2.purpose) === "attempt-submit" && !(metadata2.purpose === "diagnostic" && metadata2.diagnostic)) {
|
|
103220
|
+
throw AssessmentRuntimeError.from(assessmentFlowViolation("This assessment accepts a complete attempt, not per-item feedback."));
|
|
103221
|
+
}
|
|
103222
|
+
return context2;
|
|
103223
|
+
}
|
|
103224
|
+
function prepareAssessmentProgress(tokenContext, scope, input) {
|
|
103225
|
+
const context2 = requireAssessmentTokenContext(tokenContext, scope);
|
|
103226
|
+
const transcript = parseAssessmentTranscript(input.transcript);
|
|
103227
|
+
const progress = assessmentProgressAdministration(context2.metadata.purpose, {
|
|
103228
|
+
...input,
|
|
103229
|
+
transcript
|
|
103230
|
+
});
|
|
103231
|
+
if (!progress.ok) {
|
|
103232
|
+
throw AssessmentRuntimeError.from(progress.failure);
|
|
103233
|
+
}
|
|
103234
|
+
return { context: context2, transcript, submissionId: progress.submissionId };
|
|
103235
|
+
}
|
|
103236
|
+
function prepareAssessmentItemTranscript(assessment, input) {
|
|
103237
|
+
validateAssessmentResponseUpdate(assessment, { [input.itemIdentifier]: input.responses });
|
|
103238
|
+
const appended = appendAssessmentAdministration(input.transcript, input);
|
|
103239
|
+
if (!appended.ok) {
|
|
103240
|
+
throw AssessmentRuntimeError.from(appended.failure);
|
|
103241
|
+
}
|
|
103242
|
+
return appended;
|
|
103243
|
+
}
|
|
103244
|
+
function prepareAttemptAssessmentTranscript(context2, assessment, transcript, terminal = false) {
|
|
103245
|
+
return prepareAssessmentTranscript({
|
|
103246
|
+
transcript,
|
|
103247
|
+
attemptId: context2.attemptId,
|
|
103248
|
+
contentRevision: context2.metadata.selectedTest.contentRevision,
|
|
103249
|
+
assessment,
|
|
103250
|
+
routed: context2.metadata.purpose === "diagnostic" && context2.metadata.diagnostic !== undefined,
|
|
103251
|
+
allowIncomplete: !terminal && assessmentFlowForPurpose(context2.metadata.purpose) === "attempt-submit"
|
|
103252
|
+
});
|
|
103253
|
+
}
|
|
103254
|
+
function requireTranscriptWriteAccess(attemptId, integrationStatus) {
|
|
103255
|
+
if (integrationStatus === "deactivated") {
|
|
103256
|
+
throw AssessmentRuntimeError.from(assessmentAttemptUnauthorized(attemptId));
|
|
103257
|
+
}
|
|
103258
|
+
}
|
|
103259
|
+
function requireTranscriptVersion(transcript, expectedResponseVersion) {
|
|
103260
|
+
if (expectedResponseVersion !== transcript.administrations.length) {
|
|
103261
|
+
throw AssessmentRuntimeError.from(assessmentResponseVersionConflict(expectedResponseVersion, transcript.administrations.length));
|
|
103262
|
+
}
|
|
103263
|
+
}
|
|
103264
|
+
function requireAssessmentTranscriptCheckpoint(attempt, integrationStatus, input) {
|
|
103265
|
+
requireTranscriptWriteAccess(attempt.sourcedId, integrationStatus);
|
|
103266
|
+
requireTranscriptVersion(input.transcript, input.expectedResponseVersion);
|
|
103267
|
+
if (!isAssessmentAttemptOpen({ ...attempt, inProgress: attempt.inProgress ?? "" })) {
|
|
103268
|
+
throw AssessmentRuntimeError.from(assessmentAttemptNotOpen(attempt.sourcedId));
|
|
103269
|
+
}
|
|
103270
|
+
}
|
|
103271
|
+
function assessmentTranscriptRequestHash(input) {
|
|
103272
|
+
return sha256Hex(canonicalJson2({
|
|
103273
|
+
transcript: input.transcript,
|
|
103274
|
+
...input.session ? { session: input.session } : {}
|
|
103275
|
+
}));
|
|
103276
|
+
}
|
|
103277
|
+
function resolveAssessmentTranscriptSubmission(attempt, metadata2, integrationStatus, input, requestHash) {
|
|
103278
|
+
const disposition = classifyAssessmentSubmission({
|
|
103279
|
+
inProgress: attempt.inProgress ?? "",
|
|
103280
|
+
scoreStatus: attempt.scoreStatus,
|
|
103281
|
+
submissionId: metadata2.submissionId
|
|
103282
|
+
}, input.submissionId);
|
|
103283
|
+
if (disposition === "replay" && metadata2.completion?.requestHash === requestHash) {
|
|
103284
|
+
return "replay";
|
|
103285
|
+
}
|
|
103286
|
+
if (disposition === "reject") {
|
|
103287
|
+
throw AssessmentRuntimeError.from(assessmentAlreadySubmitted(attempt.sourcedId));
|
|
103288
|
+
}
|
|
103289
|
+
if (disposition !== "submit") {
|
|
103290
|
+
throw AssessmentRuntimeError.from(assessmentFlowViolation("A different completion was already accepted for this attempt."));
|
|
103291
|
+
}
|
|
103292
|
+
requireTranscriptWriteAccess(attempt.sourcedId, integrationStatus);
|
|
103293
|
+
if (input.session && input.session.runId !== attempt.sourcedId) {
|
|
103294
|
+
throw new ValidationError("Assessment session runId must match the attemptId.");
|
|
103295
|
+
}
|
|
103296
|
+
requireTranscriptVersion(input.transcript, input.expectedResponseVersion);
|
|
103297
|
+
return "submit";
|
|
103298
|
+
}
|
|
103299
|
+
function buildGradedAssessmentTranscript(input) {
|
|
103300
|
+
const { context: context2, prepared, itemResults, assessment, submittedAt, manifest } = input;
|
|
103301
|
+
const completed = completeAssessmentTranscript({
|
|
103302
|
+
transcript: prepared.transcript,
|
|
103303
|
+
scores: itemResults,
|
|
103304
|
+
purpose: context2.metadata.purpose,
|
|
103305
|
+
itemCount: assessment.items.length,
|
|
103306
|
+
submittedAt,
|
|
103307
|
+
manifest,
|
|
103308
|
+
terminal: input.terminal
|
|
103309
|
+
});
|
|
103310
|
+
const common2 = {
|
|
103311
|
+
...context2.metadata,
|
|
103312
|
+
responses: prepared.responses,
|
|
103313
|
+
responseVersion: completed.itemSubmissions.length,
|
|
103314
|
+
itemSubmissions: completed.itemSubmissions,
|
|
103315
|
+
updatedAt: submittedAt,
|
|
103316
|
+
transcript: prepared.transcript
|
|
103317
|
+
};
|
|
103318
|
+
const diagnostic = context2.metadata.purpose === "diagnostic" ? context2.metadata.diagnostic : undefined;
|
|
103319
|
+
const metadata2 = diagnostic ? {
|
|
103320
|
+
...common2,
|
|
103321
|
+
purpose: "diagnostic",
|
|
103322
|
+
diagnostic: { ...diagnostic, ledger: completed.ledger, state: completed.state }
|
|
103323
|
+
} : common2;
|
|
103324
|
+
return {
|
|
103325
|
+
metadata: metadata2,
|
|
103326
|
+
transcript: prepared.transcript,
|
|
103327
|
+
assessment,
|
|
103328
|
+
itemResults,
|
|
103329
|
+
manifest,
|
|
103330
|
+
score: aggregateAssessmentScore(itemResults.map((item) => item.score.earned), itemResults.reduce((sum, item) => sum + item.score.possible, 0)),
|
|
103331
|
+
correctQuestions: countCorrectQuestions(itemResults.map((item) => item.isCorrect))
|
|
103332
|
+
};
|
|
103333
|
+
}
|
|
103334
|
+
function assessmentTranscriptRuntimeError(error88) {
|
|
103335
|
+
return error88 instanceof AssessmentTranscriptError ? AssessmentRuntimeError.from({
|
|
103336
|
+
code: error88.code,
|
|
103337
|
+
message: error88.message,
|
|
103338
|
+
details: { transcriptIndex: error88.index }
|
|
103339
|
+
}) : error88;
|
|
103340
|
+
}
|
|
103341
|
+
function buildAssessmentTranscriptFeedback(attemptId, graded, index2) {
|
|
103342
|
+
const { metadata: metadata2 } = graded;
|
|
103343
|
+
const submission = metadata2.itemSubmissions[index2];
|
|
103344
|
+
const common2 = {
|
|
103345
|
+
attemptId,
|
|
103346
|
+
responseVersion: metadata2.responseVersion,
|
|
103347
|
+
status: "in_progress",
|
|
103348
|
+
transcript: graded.transcript
|
|
103349
|
+
};
|
|
103350
|
+
return metadata2.purpose === "diagnostic" && metadata2.diagnostic ? {
|
|
103351
|
+
...common2,
|
|
103352
|
+
routing: projectDiagnosticRoutingSnapshot(metadata2.diagnostic.routingRevision, metadata2.diagnostic.state),
|
|
103353
|
+
submission: diagnosticReceipt(submission, metadata2.diagnostic.ledger[index2])
|
|
103354
|
+
} : {
|
|
103355
|
+
...common2,
|
|
103356
|
+
responses: metadata2.responses,
|
|
103357
|
+
itemSubmissions: metadata2.itemSubmissions,
|
|
103358
|
+
submission
|
|
103359
|
+
};
|
|
103360
|
+
}
|
|
103361
|
+
async function resumeAssessmentTranscript(input) {
|
|
103362
|
+
const retained = input.suppliedTranscript ?? input.checkpointTranscript;
|
|
103363
|
+
let transcript = retained ?? await input.recover();
|
|
103364
|
+
while (true) {
|
|
103365
|
+
try {
|
|
103366
|
+
return await input.grade(transcript);
|
|
103367
|
+
} catch (error88) {
|
|
103368
|
+
const index2 = error88 instanceof AssessmentRuntimeError && isRecord(error88.details) ? error88.details.transcriptIndex : undefined;
|
|
103369
|
+
if (retained !== undefined || typeof index2 !== "number" || index2 < 0 || index2 >= transcript.administrations.length) {
|
|
103370
|
+
throw error88;
|
|
103318
103371
|
}
|
|
103319
|
-
|
|
103320
|
-
|
|
103321
|
-
|
|
103372
|
+
transcript = {
|
|
103373
|
+
...transcript,
|
|
103374
|
+
administrations: transcript.administrations.slice(0, index2)
|
|
103375
|
+
};
|
|
103322
103376
|
}
|
|
103323
103377
|
}
|
|
103324
|
-
|
|
103325
|
-
|
|
103326
|
-
|
|
103327
|
-
|
|
103328
|
-
|
|
103329
|
-
|
|
103330
|
-
|
|
103331
|
-
|
|
103332
|
-
|
|
103333
|
-
|
|
103334
|
-
|
|
103335
|
-
|
|
103336
|
-
|
|
103337
|
-
|
|
103338
|
-
|
|
103339
|
-
|
|
103340
|
-
|
|
103341
|
-
|
|
103342
|
-
|
|
103343
|
-
|
|
103344
|
-
|
|
103345
|
-
|
|
103346
|
-
|
|
103347
|
-
|
|
103348
|
-
|
|
103349
|
-
|
|
103350
|
-
|
|
103351
|
-
|
|
103352
|
-
|
|
103353
|
-
|
|
103354
|
-
|
|
103355
|
-
|
|
103356
|
-
|
|
103357
|
-
|
|
103358
|
-
|
|
103359
|
-
|
|
103360
|
-
|
|
103361
|
-
isCorrect: assessmentItemCorrectness(interactionResults.map((result) => result.isCorrect)),
|
|
103362
|
-
grading: omittedResponseCount > 0 ? PLATFORM_QTI_ADAPTER_CHILD_GRADING : QTI_CHILD_GRADING
|
|
103363
|
-
});
|
|
103364
|
-
}
|
|
103365
|
-
if (item.requests.length === 0) {
|
|
103366
|
-
completeItem();
|
|
103367
|
-
return [];
|
|
103378
|
+
}
|
|
103379
|
+
function assessmentReviewTranscriptOutcomes(input) {
|
|
103380
|
+
const resultByItem = new Map(input.itemResults.map((item) => [item.itemIdentifier, item]));
|
|
103381
|
+
const administered = administeredItemIdentifiers(input.metadata.itemSubmissions);
|
|
103382
|
+
return input.metadata.review.selections.filter((selection) => administered.has(selection.itemIdentifier)).map((selection) => {
|
|
103383
|
+
const result = resultByItem.get(selection.itemIdentifier);
|
|
103384
|
+
const submission = itemAdministration(input.metadata.itemSubmissions, selection.itemIdentifier);
|
|
103385
|
+
if (!result || !submission) {
|
|
103386
|
+
throw new Error(`Scoring omitted selected review item ${selection.itemIdentifier}`);
|
|
103387
|
+
}
|
|
103388
|
+
return {
|
|
103389
|
+
itemIdentifier: selection.itemIdentifier,
|
|
103390
|
+
standard: selection.standard,
|
|
103391
|
+
answered: submission.answered,
|
|
103392
|
+
score: result.score,
|
|
103393
|
+
isCorrect: result.isCorrect
|
|
103394
|
+
};
|
|
103395
|
+
});
|
|
103396
|
+
}
|
|
103397
|
+
function assessmentReviewTranscriptExposures(input) {
|
|
103398
|
+
const exposures = [];
|
|
103399
|
+
const authoritative = new Set;
|
|
103400
|
+
for (const { result, metadata: metadata2 } of input.attempts) {
|
|
103401
|
+
const state = { ...result, inProgress: result.inProgress ?? "" };
|
|
103402
|
+
const settled = isAssessmentAttemptAwaitingAward(state) || isAssessmentAttemptCompleted(state);
|
|
103403
|
+
const useTranscript = !settled && metadata2.transcript !== undefined;
|
|
103404
|
+
if (metadata2.purpose === "review" && (useTranscript || settled)) {
|
|
103405
|
+
authoritative.add(result.sourcedId);
|
|
103406
|
+
if (metadata2.review.bankRevision === input.bankRevision) {
|
|
103407
|
+
const accepted = useTranscript ? metadata2.transcript.administrations.map((entry) => ({
|
|
103408
|
+
itemIdentifier: entry.itemIdentifier,
|
|
103409
|
+
deliveredAt: metadata2.updatedAt
|
|
103410
|
+
})) : metadata2.itemSubmissions.map((submission) => ({
|
|
103411
|
+
itemIdentifier: submission.itemIdentifier,
|
|
103412
|
+
deliveredAt: submission.submittedAt
|
|
103413
|
+
}));
|
|
103414
|
+
exposures.push(...accepted);
|
|
103368
103415
|
}
|
|
103369
|
-
|
|
103370
|
-
startedAt ??= Date.now();
|
|
103371
|
-
try {
|
|
103372
|
-
const response = responses[item.itemIdentifier]?.[request.identifier];
|
|
103373
|
-
if (response === undefined) {
|
|
103374
|
-
syntheticIncorrectInteractionCount += 1;
|
|
103375
|
-
interactionResults[index2] = { score: 0, isCorrect: false };
|
|
103376
|
-
} else {
|
|
103377
|
-
qtiRemoteInteractionCount += 1;
|
|
103378
|
-
interactionResults[index2] = await processResponse(item.itemIdentifier, request);
|
|
103379
|
-
}
|
|
103380
|
-
completedInteractions += 1;
|
|
103381
|
-
if (completedInteractions === item.requests.length && !failed) {
|
|
103382
|
-
completeItem();
|
|
103383
|
-
}
|
|
103384
|
-
} catch (error88) {
|
|
103385
|
-
if (!failed) {
|
|
103386
|
-
failed = true;
|
|
103387
|
-
fallbackEvent({
|
|
103388
|
-
"app.assessment.fallback_outcome": "failed",
|
|
103389
|
-
"exception.type": errorType(error88),
|
|
103390
|
-
"app.error.message": errorMessage(error88)
|
|
103391
|
-
});
|
|
103392
|
-
}
|
|
103393
|
-
throw error88;
|
|
103394
|
-
}
|
|
103395
|
-
});
|
|
103396
|
-
});
|
|
103397
|
-
await runWithConcurrency(fallbackTasks, SCORING_CONCURRENCY, (task) => task());
|
|
103416
|
+
}
|
|
103398
103417
|
}
|
|
103399
|
-
|
|
103400
|
-
const
|
|
103401
|
-
if (!
|
|
103402
|
-
|
|
103418
|
+
for (const command of input.children) {
|
|
103419
|
+
const { attempt, administration, context: context2 } = command.payload;
|
|
103420
|
+
if (!authoritative.has(attempt.attemptId) && context2.kind === "review" && context2.bankRevision === input.bankRevision) {
|
|
103421
|
+
exposures.push({
|
|
103422
|
+
itemIdentifier: administration.itemIdentifier,
|
|
103423
|
+
deliveredAt: command.recordedAt
|
|
103424
|
+
});
|
|
103403
103425
|
}
|
|
103404
|
-
|
|
103405
|
-
|
|
103426
|
+
}
|
|
103427
|
+
return exposures;
|
|
103406
103428
|
}
|
|
103407
|
-
var
|
|
103408
|
-
var init_timeback_assessment_scoring_util = __esm(() => {
|
|
103429
|
+
var init_timeback_assessment_transcript_util = __esm(() => {
|
|
103409
103430
|
init_assessment_runtime2();
|
|
103410
|
-
|
|
103431
|
+
init_errors2();
|
|
103411
103432
|
init_timeback_assessment_runtime_util();
|
|
103412
|
-
QTI_CHILD_GRADING = {
|
|
103413
|
-
source: "timeback-qti",
|
|
103414
|
-
graderVersion: "timeback-qti-process-response-v1"
|
|
103415
|
-
};
|
|
103416
|
-
PLATFORM_QTI_ADAPTER_CHILD_GRADING = {
|
|
103417
|
-
source: "platform-qti-adapter",
|
|
103418
|
-
graderVersion: "platform-qti-adapter-v1",
|
|
103419
|
-
qtiGraderVersion: "timeback-qti-process-response-v1"
|
|
103420
|
-
};
|
|
103421
|
-
PLATFORM_CHILD_GRADING = {
|
|
103422
|
-
source: "platform-artifact",
|
|
103423
|
-
artifactVersion: `assessment-artifact-v${ASSESSMENT_ARTIFACT_SCHEMA_VERSION}`,
|
|
103424
|
-
graderVersion: `platform-grader-v${ASSESSMENT_GRADER_PROTOCOL_VERSION}`
|
|
103425
|
-
};
|
|
103426
103433
|
});
|
|
103427
103434
|
|
|
103428
103435
|
// ../api-core/src/utils/timeback-qti-hydration.util.ts
|
|
@@ -103509,20 +103516,19 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
103509
103516
|
init_assessment_artifact_runtime_util();
|
|
103510
103517
|
init_assessment_runtime_lock_util();
|
|
103511
103518
|
init_timeback_assessment_artifacts_util();
|
|
103519
|
+
init_timeback_assessment_child_result_util();
|
|
103512
103520
|
init_timeback_assessment_preparation_util();
|
|
103513
103521
|
init_timeback_assessment_publication_util();
|
|
103514
|
-
init_timeback_assessment_reconciliation_util();
|
|
103515
|
-
init_timeback_assessment_replay_util();
|
|
103516
103522
|
init_timeback_assessment_routing_util();
|
|
103517
103523
|
init_timeback_assessment_rules_util();
|
|
103518
103524
|
init_timeback_assessment_runtime_util();
|
|
103519
103525
|
init_timeback_assessment_scoring_util();
|
|
103526
|
+
init_timeback_assessment_transcript_util();
|
|
103520
103527
|
init_timeback_qti_hydration_util();
|
|
103521
103528
|
await __promiseAll([
|
|
103522
103529
|
init_dist5(),
|
|
103523
103530
|
init_errors8(),
|
|
103524
|
-
init_assessment_artifact_loader_util()
|
|
103525
|
-
init_timeback_assessment_child_result_util()
|
|
103531
|
+
init_assessment_artifact_loader_util()
|
|
103526
103532
|
]);
|
|
103527
103533
|
AssessmentPreparationPrerequisiteError = class AssessmentPreparationPrerequisiteError extends Error {
|
|
103528
103534
|
original;
|
|
@@ -103537,8 +103543,6 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
103537
103543
|
static PREPARATION_IN_FLIGHT_LIMIT = 32;
|
|
103538
103544
|
static ASSESSMENT_CACHE_TTL_MS = 60000;
|
|
103539
103545
|
static EXPORT_CONCURRENCY = 4;
|
|
103540
|
-
static ITEM_SUBMISSION_MAX_PREPARATION_ATTEMPTS = 3;
|
|
103541
|
-
static ASSESSMENT_VERSION_CONFIRMATION_DELAY_MS = 25;
|
|
103542
103546
|
static SCORING_CONCURRENCY = 4;
|
|
103543
103547
|
static STALE_AWARD_AGE_MS = 15 * 60 * 1000;
|
|
103544
103548
|
deps;
|
|
@@ -103555,6 +103559,14 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
103555
103559
|
maxSize: TimebackAssessmentRuntimeService.ASSESSMENT_CACHE_LIMIT
|
|
103556
103560
|
});
|
|
103557
103561
|
artifactLoader;
|
|
103562
|
+
itemScoreCache = new TimebackCache({
|
|
103563
|
+
defaultTTL: 5 * 60000,
|
|
103564
|
+
maxSize: 512
|
|
103565
|
+
});
|
|
103566
|
+
childLineItemCache = new TimebackCache({
|
|
103567
|
+
defaultTTL: 5 * 60000,
|
|
103568
|
+
maxSize: 512
|
|
103569
|
+
});
|
|
103558
103570
|
preparations = new AssessmentPreparationFlights(TimebackAssessmentRuntimeService.PREPARATION_IN_FLIGHT_LIMIT);
|
|
103559
103571
|
constructor(deps) {
|
|
103560
103572
|
this.deps = deps;
|
|
@@ -103620,8 +103632,6 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
103620
103632
|
return this.startReview({ gameId, studentId, input, user });
|
|
103621
103633
|
}
|
|
103622
103634
|
const masteryStandard = input.purpose === "mastery" ? normalizeMasteryStandard(input.standard) : undefined;
|
|
103623
|
-
const requestFingerprint = this.startRequestFingerprint(input);
|
|
103624
|
-
const pendingSupersessions = new Set;
|
|
103625
103635
|
const candidates = await this.candidateIntegrations(gameId, studentId, input);
|
|
103626
103636
|
const lockKeys = candidates.rows.map((row) => assessmentRuntimeStartLockKey({
|
|
103627
103637
|
studentId,
|
|
@@ -103630,7 +103640,7 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
103630
103640
|
courseId: row.courseId
|
|
103631
103641
|
}));
|
|
103632
103642
|
return this.deps.assessmentRuntimeLock(this.deps.db, lockKeys, async (db2) => {
|
|
103633
|
-
const context2 = await this.selectIntegration(candidates,
|
|
103643
|
+
const context2 = await this.selectIntegration(candidates, input);
|
|
103634
103644
|
const attemptScope = {
|
|
103635
103645
|
studentId,
|
|
103636
103646
|
activityId: input.activityId,
|
|
@@ -103643,44 +103653,34 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
103643
103653
|
this.liveTests(context2.integration.id, input.purpose, db2, masteryStandard),
|
|
103644
103654
|
this.listAttempts(attemptScope)
|
|
103645
103655
|
]);
|
|
103646
|
-
const
|
|
103647
|
-
|
|
103648
|
-
|
|
103649
|
-
|
|
103650
|
-
|
|
103651
|
-
}
|
|
103652
|
-
|
|
103653
|
-
|
|
103654
|
-
|
|
103655
|
-
|
|
103656
|
-
|
|
103657
|
-
|
|
103658
|
-
|
|
103659
|
-
|
|
103660
|
-
|
|
103661
|
-
|
|
103662
|
-
if (assessment.identifier !== decision.test.qtiTestIdentifier) {
|
|
103663
|
-
assessment = await this.loadAssessment(decision.test.qtiTestIdentifier);
|
|
103664
|
-
}
|
|
103665
|
-
const classId = await this.resolveClassId(context2.integration.courseId, context2.enrollment, studentId);
|
|
103656
|
+
const pending = await this.pendingAwardSnapshot(listing, context2);
|
|
103657
|
+
if (pending) {
|
|
103658
|
+
return pending;
|
|
103659
|
+
}
|
|
103660
|
+
const decision = this.selectFreshStart(tests, listing, input.purpose);
|
|
103661
|
+
const [assessment, classId, { attemptNumber, attemptId }] = await Promise.all([
|
|
103662
|
+
this.loadAssessment(decision.test.qtiTestIdentifier),
|
|
103663
|
+
this.resolveClassId(context2.integration.courseId, context2.enrollment, studentId),
|
|
103664
|
+
this.claimAttemptId({
|
|
103665
|
+
gameId,
|
|
103666
|
+
studentId,
|
|
103667
|
+
courseId: context2.integration.courseId,
|
|
103668
|
+
activityId: input.activityId,
|
|
103669
|
+
purpose: input.purpose
|
|
103670
|
+
}, nextAssessmentAttemptNumber(listing.attempts.values()))
|
|
103671
|
+
]);
|
|
103666
103672
|
const lineItemId = await this.ensureLineItem({
|
|
103667
103673
|
classId,
|
|
103668
103674
|
integration: context2.integration,
|
|
103669
103675
|
purpose: input.purpose,
|
|
103670
103676
|
assessment
|
|
103671
103677
|
});
|
|
103672
|
-
const { attemptNumber, attemptId } = await this.claimAttemptId({
|
|
103673
|
-
gameId,
|
|
103674
|
-
studentId,
|
|
103675
|
-
courseId: context2.integration.courseId,
|
|
103676
|
-
activityId: input.activityId,
|
|
103677
|
-
purpose: input.purpose
|
|
103678
|
-
}, Math.max(0, ...[...refreshed.values()].map((entry) => entry.metadata.attemptNumber)) + 1);
|
|
103679
103678
|
const timestamp6 = new Date().toISOString();
|
|
103680
103679
|
const metadataBase = {
|
|
103681
103680
|
version: PLAYCADEMY_ASSESSMENT_RESULT_METADATA_VERSION,
|
|
103682
103681
|
activityId: input.activityId,
|
|
103683
103682
|
courseId: context2.integration.courseId,
|
|
103683
|
+
courseName: context2.enrollment.course.title,
|
|
103684
103684
|
integrationId: context2.integration.id,
|
|
103685
103685
|
enrollmentId: context2.enrollment.id,
|
|
103686
103686
|
selectedTest: {
|
|
@@ -103699,7 +103699,7 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
103699
103699
|
...metadataBase,
|
|
103700
103700
|
purpose: input.purpose,
|
|
103701
103701
|
mastery: {
|
|
103702
|
-
requestFingerprint,
|
|
103702
|
+
requestFingerprint: masteryRequestFingerprint(input),
|
|
103703
103703
|
standard: masteryStandard
|
|
103704
103704
|
}
|
|
103705
103705
|
} : { ...metadataBase, purpose: input.purpose };
|
|
@@ -103717,8 +103717,11 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
103717
103717
|
grade: context2.integration.grade
|
|
103718
103718
|
})
|
|
103719
103719
|
});
|
|
103720
|
-
await
|
|
103721
|
-
|
|
103720
|
+
const [snapshot] = await Promise.all([
|
|
103721
|
+
this.snapshot(result, metadata2, assessment, context2.integration),
|
|
103722
|
+
this.supersedeReplacedAttempts(db2, listing.attempts, context2.enrollment.id)
|
|
103723
|
+
]);
|
|
103724
|
+
return snapshot;
|
|
103722
103725
|
});
|
|
103723
103726
|
}
|
|
103724
103727
|
async prepareNewAttempt(params, requestHash) {
|
|
@@ -103727,7 +103730,7 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
103727
103730
|
this.candidateIntegrations(params.gameId, params.studentId, params.input),
|
|
103728
103731
|
this.studentAssessmentResults(params.studentId)
|
|
103729
103732
|
]);
|
|
103730
|
-
const context2 = await this.selectIntegration(candidates, params.
|
|
103733
|
+
const context2 = await this.selectIntegration(candidates, params.input);
|
|
103731
103734
|
const attemptScope = {
|
|
103732
103735
|
studentId: params.studentId,
|
|
103733
103736
|
activityId: params.input.activityId,
|
|
@@ -103737,29 +103740,31 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
103737
103740
|
enrollmentId: context2.enrollment.id
|
|
103738
103741
|
};
|
|
103739
103742
|
const tests = await this.liveTests(context2.integration.id, params.input.purpose, this.deps.db, params.input.purpose === "mastery" ? params.input.standard : undefined, params.input.purpose === "diagnostic" ? params.input.diagnosticKey : undefined);
|
|
103740
|
-
const listing = this.attemptListing(results, attemptScope);
|
|
103741
|
-
|
|
103742
|
-
|
|
103743
|
-
|
|
103744
|
-
const selection = resolveAssessmentPreparationSelection(decision, candidates.rows.length);
|
|
103745
|
-
if (selection.status === "blocked") {
|
|
103746
|
-
this.recordPreparation(selection.result.status, params.input.purpose, startedAt, {
|
|
103747
|
-
reason: selection.result.status === "canonical_required" ? selection.result.reason : "multiple_candidate_courses"
|
|
103743
|
+
const listing = await this.attemptListing(results, attemptScope);
|
|
103744
|
+
if (this.pendingAwardAttempt(listing, context2.enrollment.id)) {
|
|
103745
|
+
this.recordPreparation("unprepared", params.input.purpose, startedAt, {
|
|
103746
|
+
reason: "pending_award"
|
|
103748
103747
|
});
|
|
103749
|
-
return
|
|
103748
|
+
return { status: "unprepared", reason: "prerequisite_unavailable" };
|
|
103750
103749
|
}
|
|
103751
|
-
if (
|
|
103752
|
-
|
|
103750
|
+
if (candidates.rows.length !== 1) {
|
|
103751
|
+
this.recordPreparation("unprepared", params.input.purpose, startedAt, {
|
|
103752
|
+
reason: "multiple_candidate_courses"
|
|
103753
|
+
});
|
|
103754
|
+
return { status: "unprepared", reason: "prerequisite_unavailable" };
|
|
103753
103755
|
}
|
|
103754
103756
|
const preparation = {
|
|
103755
103757
|
context: context2,
|
|
103756
103758
|
tests,
|
|
103757
103759
|
listing,
|
|
103758
|
-
decision:
|
|
103760
|
+
decision: this.selectFreshStart(tests, listing, params.input.purpose)
|
|
103759
103761
|
};
|
|
103760
103762
|
let prepared;
|
|
103761
103763
|
try {
|
|
103762
103764
|
prepared = await this.buildPreparationPlan(params, requestHash, preparation);
|
|
103765
|
+
if (!await this.loadPreparedPlanAssessment(prepared.plan)) {
|
|
103766
|
+
throw new Error("Prepared assessment artifact is unavailable");
|
|
103767
|
+
}
|
|
103763
103768
|
} catch (error88) {
|
|
103764
103769
|
if (error88 instanceof ValidationError || error88 instanceof AssessmentRuntimeError) {
|
|
103765
103770
|
throw error88;
|
|
@@ -103845,8 +103850,10 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
103845
103850
|
integrationId: context2.integration.id,
|
|
103846
103851
|
enrollmentId: context2.enrollment.id,
|
|
103847
103852
|
courseId: context2.integration.courseId,
|
|
103853
|
+
courseName: context2.enrollment.course.title,
|
|
103848
103854
|
...attempt,
|
|
103849
103855
|
parentLineItemId,
|
|
103856
|
+
supersedes: [...this.replacedAttemptIds(listing.attempts, context2.enrollment.id)],
|
|
103850
103857
|
selectedTest: {
|
|
103851
103858
|
assessmentKey: decision.test.assessmentKey,
|
|
103852
103859
|
identifier: assessment.identifier,
|
|
@@ -103885,7 +103892,9 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
103885
103892
|
const selected = selectReviewItems({
|
|
103886
103893
|
request: params.input,
|
|
103887
103894
|
bank: catalog.bank,
|
|
103888
|
-
exposures:
|
|
103895
|
+
exposures: assessmentReviewTranscriptExposures({
|
|
103896
|
+
attempts: listing.attempts.values(),
|
|
103897
|
+
children: listing.reviewChildren.values(),
|
|
103889
103898
|
bankRevision: catalog.bank.bankRevision
|
|
103890
103899
|
})
|
|
103891
103900
|
});
|
|
@@ -103896,26 +103905,12 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
103896
103905
|
throw AssessmentRuntimeError.from(assessmentReviewBankShortage(selected.fulfillment));
|
|
103897
103906
|
}
|
|
103898
103907
|
const artifact = await this.loadOrCompileReviewBankArtifact(catalog);
|
|
103899
|
-
const assessment = projectReviewAssessment(artifact.assessment, catalog.bank, selected.selections);
|
|
103900
103908
|
const source = { assessment: artifact.assessment, bank: catalog.bank };
|
|
103901
103909
|
const parentLineItemId = await this.provisionPreparationPrerequisites(() => this.ensureReviewBankLineItem({
|
|
103902
103910
|
integration: context2.integration,
|
|
103903
103911
|
activityId: params.input.activityId,
|
|
103904
103912
|
source
|
|
103905
103913
|
}));
|
|
103906
|
-
await this.provisionPreparationPrerequisites(() => runWithConcurrency(assessment.items, TimebackAssessmentRuntimeService.SCORING_CONCURRENCY, (item) => {
|
|
103907
|
-
const indexed = catalog.bank.items.find((candidate) => candidate.itemIdentifier === item.identifier);
|
|
103908
|
-
if (!indexed) {
|
|
103909
|
-
throw new Error(`Review-bank index is missing item ${item.identifier}`);
|
|
103910
|
-
}
|
|
103911
|
-
return this.ensureReviewQuestionLineItem({
|
|
103912
|
-
parentLineItemId,
|
|
103913
|
-
integration: context2.integration,
|
|
103914
|
-
bankRevision: catalog.bank.bankRevision,
|
|
103915
|
-
standards: [...indexed.standards],
|
|
103916
|
-
item
|
|
103917
|
-
});
|
|
103918
|
-
}));
|
|
103919
103914
|
const attempt = await this.provisionPreparationPrerequisites(() => this.claimAttemptId({
|
|
103920
103915
|
gameId: params.gameId,
|
|
103921
103916
|
studentId: params.studentId,
|
|
@@ -103934,8 +103929,10 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
103934
103929
|
integrationId: context2.integration.id,
|
|
103935
103930
|
enrollmentId: context2.enrollment.id,
|
|
103936
103931
|
courseId: context2.integration.courseId,
|
|
103932
|
+
courseName: context2.enrollment.course.title,
|
|
103937
103933
|
...attempt,
|
|
103938
103934
|
parentLineItemId,
|
|
103935
|
+
supersedes: [...this.replacedAttemptIds(listing.attempts, context2.enrollment.id)],
|
|
103939
103936
|
selectedTest: {
|
|
103940
103937
|
assessmentKey: decision.test.assessmentKey,
|
|
103941
103938
|
identifier: artifact.assessment.identifier,
|
|
@@ -103984,10 +103981,7 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
103984
103981
|
if (!integration || !await this.preparedPlanTestIsLive(verified, db2)) {
|
|
103985
103982
|
return null;
|
|
103986
103983
|
}
|
|
103987
|
-
|
|
103988
|
-
return null;
|
|
103989
|
-
}
|
|
103990
|
-
return this.createPreparedAttempt(verified, assessment, integration);
|
|
103984
|
+
return this.claimPreparedAttempt(db2, verified, assessment, integration);
|
|
103991
103985
|
});
|
|
103992
103986
|
if (!outcome) {
|
|
103993
103987
|
this.recordPreparedStart("fallback", params.input.purpose, startedAt, {
|
|
@@ -104012,8 +104006,6 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
104012
104006
|
studentId,
|
|
104013
104007
|
input
|
|
104014
104008
|
}) {
|
|
104015
|
-
const requestFingerprint = this.startRequestFingerprint(input);
|
|
104016
|
-
const pendingSupersessions = new Set;
|
|
104017
104009
|
const candidates = await this.candidateIntegrations(gameId, studentId, input);
|
|
104018
104010
|
const lockKeys = candidates.rows.map((row) => assessmentRuntimeStartLockKey({
|
|
104019
104011
|
studentId,
|
|
@@ -104022,7 +104014,7 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
104022
104014
|
courseId: row.courseId
|
|
104023
104015
|
}));
|
|
104024
104016
|
return this.deps.assessmentRuntimeLock(this.deps.db, lockKeys, async (db2) => {
|
|
104025
|
-
const context2 = await this.selectIntegration(candidates,
|
|
104017
|
+
const context2 = await this.selectIntegration(candidates, input);
|
|
104026
104018
|
const attemptScope = {
|
|
104027
104019
|
studentId,
|
|
104028
104020
|
activityId: input.activityId,
|
|
@@ -104035,32 +104027,25 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
104035
104027
|
this.liveTests(context2.integration.id, input.purpose, db2, undefined, input.diagnosticKey),
|
|
104036
104028
|
this.listAttempts(attemptScope)
|
|
104037
104029
|
]);
|
|
104038
|
-
const
|
|
104039
|
-
if (
|
|
104040
|
-
|
|
104041
|
-
return initialResolution.snapshot;
|
|
104030
|
+
const pending = await this.pendingAwardSnapshot(listing, context2);
|
|
104031
|
+
if (pending) {
|
|
104032
|
+
return pending;
|
|
104042
104033
|
}
|
|
104043
|
-
|
|
104034
|
+
const decision = this.selectFreshStart(tests, listing, input.purpose);
|
|
104044
104035
|
let definition = await this.requireDiagnosticDefinition(decision.test.id, context2.integration.id, db2, true);
|
|
104045
|
-
|
|
104046
|
-
|
|
104047
|
-
|
|
104048
|
-
|
|
104049
|
-
|
|
104050
|
-
|
|
104051
|
-
|
|
104052
|
-
|
|
104053
|
-
|
|
104054
|
-
|
|
104055
|
-
|
|
104056
|
-
|
|
104057
|
-
|
|
104058
|
-
routing = await this.initializeHostedDiagnostic(definition, assessment);
|
|
104059
|
-
} else {
|
|
104060
|
-
definition = await this.requireDiagnosticDefinition(definition.id, context2.integration.id, db2, true);
|
|
104061
|
-
routing = await this.initializeHostedDiagnostic(definition, assessment);
|
|
104062
|
-
}
|
|
104063
|
-
const classId = await this.resolveClassId(context2.integration.courseId, context2.enrollment, studentId);
|
|
104036
|
+
const [assessment, classId, { attemptNumber, attemptId }] = await Promise.all([
|
|
104037
|
+
this.loadAssessment(definition.qtiTestIdentifier),
|
|
104038
|
+
this.resolveClassId(context2.integration.courseId, context2.enrollment, studentId),
|
|
104039
|
+
this.claimAttemptId({
|
|
104040
|
+
gameId,
|
|
104041
|
+
studentId,
|
|
104042
|
+
courseId: context2.integration.courseId,
|
|
104043
|
+
activityId: input.activityId,
|
|
104044
|
+
purpose: input.purpose
|
|
104045
|
+
}, nextAssessmentAttemptNumber(listing.attempts.values()))
|
|
104046
|
+
]);
|
|
104047
|
+
definition = await this.requireDiagnosticDefinition(definition.id, context2.integration.id, db2, true);
|
|
104048
|
+
const routing = await this.initializeHostedDiagnostic(definition, assessment);
|
|
104064
104049
|
const lineItemId = await this.ensureLineItem({
|
|
104065
104050
|
classId,
|
|
104066
104051
|
integration: context2.integration,
|
|
@@ -104072,19 +104057,13 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
104072
104057
|
routingRevision: routing.routingRevision
|
|
104073
104058
|
}
|
|
104074
104059
|
});
|
|
104075
|
-
const { attemptNumber, attemptId } = await this.claimAttemptId({
|
|
104076
|
-
gameId,
|
|
104077
|
-
studentId,
|
|
104078
|
-
courseId: context2.integration.courseId,
|
|
104079
|
-
activityId: input.activityId,
|
|
104080
|
-
purpose: input.purpose
|
|
104081
|
-
}, Math.max(0, ...[...refreshed.values()].map((entry) => entry.metadata.attemptNumber)) + 1);
|
|
104082
104060
|
const timestamp6 = new Date().toISOString();
|
|
104083
104061
|
const metadata2 = {
|
|
104084
104062
|
version: PLAYCADEMY_ASSESSMENT_RESULT_METADATA_VERSION,
|
|
104085
104063
|
activityId: input.activityId,
|
|
104086
104064
|
purpose: "diagnostic",
|
|
104087
104065
|
courseId: context2.integration.courseId,
|
|
104066
|
+
courseName: context2.enrollment.course.title,
|
|
104088
104067
|
integrationId: context2.integration.id,
|
|
104089
104068
|
enrollmentId: context2.enrollment.id,
|
|
104090
104069
|
selectedTest: {
|
|
@@ -104104,7 +104083,6 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
104104
104083
|
testName: assessment.title,
|
|
104105
104084
|
routingRevision: routing.routingRevision,
|
|
104106
104085
|
ledger: [],
|
|
104107
|
-
routingSnapshots: [],
|
|
104108
104086
|
state: routing.state
|
|
104109
104087
|
}
|
|
104110
104088
|
};
|
|
@@ -104122,8 +104100,13 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
104122
104100
|
grade: context2.integration.grade
|
|
104123
104101
|
})
|
|
104124
104102
|
});
|
|
104125
|
-
await
|
|
104126
|
-
|
|
104103
|
+
const [snapshot] = await Promise.all([
|
|
104104
|
+
this.diagnosticSnapshot(result, metadata2, assessment, context2.integration, {
|
|
104105
|
+
state: routing.state
|
|
104106
|
+
}),
|
|
104107
|
+
this.supersedeReplacedAttempts(db2, listing.attempts, context2.enrollment.id)
|
|
104108
|
+
]);
|
|
104109
|
+
return snapshot;
|
|
104127
104110
|
});
|
|
104128
104111
|
}
|
|
104129
104112
|
async startReview({
|
|
@@ -104133,7 +104116,6 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
104133
104116
|
}) {
|
|
104134
104117
|
const preparationStartedAt = Date.now();
|
|
104135
104118
|
const requestFingerprint = reviewRequestFingerprint(input, DEFAULT_REVIEW_SELECTION_POLICY);
|
|
104136
|
-
const pendingSupersessions = new Set;
|
|
104137
104119
|
const normalizedRequest = normalizeReviewRequest(input);
|
|
104138
104120
|
const candidates = await this.candidateIntegrations(gameId, studentId, input);
|
|
104139
104121
|
const lockKeys = candidates.rows.map((row) => assessmentRuntimeStartLockKey({
|
|
@@ -104143,7 +104125,7 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
104143
104125
|
courseId: row.courseId
|
|
104144
104126
|
}));
|
|
104145
104127
|
return this.deps.assessmentRuntimeLock(this.deps.db, lockKeys, async (db2) => {
|
|
104146
|
-
const context2 = await this.selectIntegration(candidates,
|
|
104128
|
+
const context2 = await this.selectIntegration(candidates, input);
|
|
104147
104129
|
const attemptScope = {
|
|
104148
104130
|
studentId,
|
|
104149
104131
|
activityId: input.activityId,
|
|
@@ -104156,40 +104138,22 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
104156
104138
|
this.liveTests(context2.integration.id, input.purpose, db2),
|
|
104157
104139
|
this.listAttempts(attemptScope)
|
|
104158
104140
|
]);
|
|
104159
|
-
const
|
|
104160
|
-
|
|
104161
|
-
|
|
104162
|
-
await this.persistPendingSupersessionsBestEffort(db2, pendingSupersessions);
|
|
104163
|
-
return initialResolution.snapshot;
|
|
104164
|
-
}
|
|
104165
|
-
let decision = initialResolution.decision;
|
|
104166
|
-
if (tests.length === 0 || !decision) {
|
|
104167
|
-
throw AssessmentRuntimeError.from(assessmentNoEligibleTests(input.purpose));
|
|
104141
|
+
const pending = await this.pendingAwardSnapshot(listing, context2);
|
|
104142
|
+
if (pending) {
|
|
104143
|
+
return pending;
|
|
104168
104144
|
}
|
|
104145
|
+
const decision = this.selectFreshStart(tests, listing, input.purpose);
|
|
104169
104146
|
if (tests.length > 1) {
|
|
104170
104147
|
throw new ValidationError("Standards review requires exactly one live review-bank assessment.");
|
|
104171
104148
|
}
|
|
104172
104149
|
const manifestStartedAt = Date.now();
|
|
104173
|
-
|
|
104150
|
+
const catalog = await this.loadReviewBankCatalog(decision.test.qtiTestIdentifier);
|
|
104174
104151
|
this.recordReviewPreparationPhase("manifest_load", manifestStartedAt, {
|
|
104175
104152
|
candidateCount: catalog.bank.items.length
|
|
104176
104153
|
});
|
|
104177
|
-
const
|
|
104178
|
-
|
|
104179
|
-
|
|
104180
|
-
if (refreshedResolution.kind === "resumed") {
|
|
104181
|
-
await this.persistPendingSupersessionsBestEffort(db2, pendingSupersessions);
|
|
104182
|
-
return refreshedResolution.snapshot;
|
|
104183
|
-
}
|
|
104184
|
-
const refreshedDecision = refreshedResolution.decision;
|
|
104185
|
-
if (!refreshedDecision || refreshedDecision.kind !== "start") {
|
|
104186
|
-
throw AssessmentRuntimeError.from(assessmentNoEligibleTests(input.purpose));
|
|
104187
|
-
}
|
|
104188
|
-
decision = refreshedDecision;
|
|
104189
|
-
if (catalog.test.identifier !== decision.test.qtiTestIdentifier) {
|
|
104190
|
-
catalog = await this.loadReviewBankCatalog(decision.test.qtiTestIdentifier);
|
|
104191
|
-
}
|
|
104192
|
-
const exposures = this.reviewExposures(refreshedListing.reviewChildren.values(), {
|
|
104154
|
+
const exposures = assessmentReviewTranscriptExposures({
|
|
104155
|
+
attempts: listing.attempts.values(),
|
|
104156
|
+
children: listing.reviewChildren.values(),
|
|
104193
104157
|
bankRevision: catalog.bank.bankRevision
|
|
104194
104158
|
});
|
|
104195
104159
|
const selected = selectReviewItems({
|
|
@@ -104210,24 +104174,27 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
104210
104174
|
});
|
|
104211
104175
|
const assessment = projectReviewAssessment(source.assessment, source.bank, selected.selections);
|
|
104212
104176
|
const provisioningStartedAt = Date.now();
|
|
104213
|
-
const parentLineItemId = await
|
|
104214
|
-
|
|
104215
|
-
|
|
104216
|
-
|
|
104217
|
-
|
|
104218
|
-
|
|
104219
|
-
|
|
104220
|
-
|
|
104221
|
-
|
|
104222
|
-
|
|
104223
|
-
|
|
104224
|
-
|
|
104177
|
+
const [parentLineItemId, { attemptNumber, attemptId }] = await Promise.all([
|
|
104178
|
+
this.ensureReviewBankLineItem({
|
|
104179
|
+
integration: context2.integration,
|
|
104180
|
+
activityId: input.activityId,
|
|
104181
|
+
source
|
|
104182
|
+
}),
|
|
104183
|
+
this.claimAttemptId({
|
|
104184
|
+
gameId,
|
|
104185
|
+
studentId,
|
|
104186
|
+
courseId: context2.integration.courseId,
|
|
104187
|
+
activityId: input.activityId,
|
|
104188
|
+
purpose: input.purpose
|
|
104189
|
+
}, nextAssessmentAttemptNumber(listing.attempts.values()))
|
|
104190
|
+
]);
|
|
104225
104191
|
const timestamp6 = new Date().toISOString();
|
|
104226
104192
|
const metadata2 = {
|
|
104227
104193
|
version: PLAYCADEMY_ASSESSMENT_RESULT_METADATA_VERSION,
|
|
104228
104194
|
activityId: input.activityId,
|
|
104229
104195
|
purpose: input.purpose,
|
|
104230
104196
|
courseId: context2.integration.courseId,
|
|
104197
|
+
courseName: context2.enrollment.course.title,
|
|
104231
104198
|
integrationId: context2.integration.id,
|
|
104232
104199
|
enrollmentId: context2.enrollment.id,
|
|
104233
104200
|
selectedTest: {
|
|
@@ -104264,18 +104231,27 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
104264
104231
|
grade: context2.integration.grade
|
|
104265
104232
|
})
|
|
104266
104233
|
});
|
|
104267
|
-
await
|
|
104234
|
+
const [snapshot] = await Promise.all([
|
|
104235
|
+
this.snapshot(result, metadata2, assessment, context2.integration),
|
|
104236
|
+
this.supersedeReplacedAttempts(db2, listing.attempts, context2.enrollment.id)
|
|
104237
|
+
]);
|
|
104268
104238
|
this.recordReviewPreparationPhase("provisioning", provisioningStartedAt);
|
|
104269
104239
|
this.recordReviewPreparationPhase("total", preparationStartedAt, {
|
|
104270
104240
|
candidateCount: catalog.bank.items.length,
|
|
104271
104241
|
selectedCount: selected.selections.length
|
|
104272
104242
|
});
|
|
104273
|
-
return
|
|
104243
|
+
return snapshot;
|
|
104274
104244
|
});
|
|
104275
104245
|
}
|
|
104276
104246
|
async get(params) {
|
|
104247
|
+
return this.resume({ ...params, input: {} });
|
|
104248
|
+
}
|
|
104249
|
+
async resume({
|
|
104250
|
+
input,
|
|
104251
|
+
...params
|
|
104252
|
+
}) {
|
|
104277
104253
|
const attempt = await this.authorizeAttemptRead(params);
|
|
104278
|
-
return this.snapshotForResult(attempt.result, attempt.metadata, attempt.integration);
|
|
104254
|
+
return this.snapshotForResult(attempt.result, attempt.metadata, attempt.integration, input.transcript);
|
|
104279
104255
|
}
|
|
104280
104256
|
async latest({
|
|
104281
104257
|
gameId,
|
|
@@ -104337,496 +104313,104 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
104337
104313
|
}
|
|
104338
104314
|
async save({ input, ...params }) {
|
|
104339
104315
|
await this.deps.validateDeveloperAccess(params.user, params.gameId);
|
|
104340
|
-
try {
|
|
104341
|
-
const warmup = await this.peekAttempt(params);
|
|
104342
|
-
if (this.isOpen(warmup.result) && !this.isPlatformRoutedDiagnosticMetadata(warmup.metadata)) {
|
|
104343
|
-
await this.loadAttemptAssessment(warmup.metadata);
|
|
104344
|
-
}
|
|
104345
|
-
} catch (error88) {
|
|
104346
|
-
addEvent("assessment.save_warmup_failed", {
|
|
104347
|
-
"app.assessment.attempt_id": params.attemptId,
|
|
104348
|
-
"exception.type": errorType(error88),
|
|
104349
|
-
"app.error.message": errorMessage(error88)
|
|
104350
|
-
});
|
|
104351
|
-
}
|
|
104352
104316
|
return this.deps.assessmentRuntimeLock(this.deps.db, [assessmentRuntimeAttemptLockKey(params.attemptId)], async (db2) => {
|
|
104353
|
-
const attempt = await this.
|
|
104317
|
+
const { attempt } = await this.resolveAttemptAuthorization(params, {
|
|
104354
104318
|
db: db2,
|
|
104355
|
-
developerAccessValidated: true
|
|
104319
|
+
developerAccessValidated: true,
|
|
104320
|
+
includeEnrollment: false
|
|
104356
104321
|
});
|
|
104357
|
-
|
|
104358
|
-
|
|
104359
|
-
|
|
104360
|
-
|
|
104361
|
-
|
|
104362
|
-
|
|
104363
|
-
|
|
104364
|
-
|
|
104365
|
-
|
|
104366
|
-
|
|
104367
|
-
|
|
104368
|
-
|
|
104369
|
-
validateAssessmentDraftResponses(assessment, responses);
|
|
104370
|
-
const metadata2 = {
|
|
104371
|
-
...attempt.metadata,
|
|
104372
|
-
responses,
|
|
104373
|
-
responseVersion: attempt.metadata.responseVersion + 1,
|
|
104374
|
-
updatedAt: new Date().toISOString()
|
|
104375
|
-
};
|
|
104376
|
-
await this.putResultMetadata(attempt.result, metadata2);
|
|
104322
|
+
requireAssessmentTranscriptCheckpoint(attempt.result, attempt.integration.status, input);
|
|
104323
|
+
const graded = await this.gradeTranscript({
|
|
104324
|
+
attemptId: params.attemptId,
|
|
104325
|
+
metadata: attempt.metadata
|
|
104326
|
+
}, input.transcript, false, db2);
|
|
104327
|
+
await this.requireClient().api.oneroster.assessmentResults.upsert(params.attemptId, buildAssessmentResultCheckpoint({
|
|
104328
|
+
result: attempt.result,
|
|
104329
|
+
metadata: attempt.metadata,
|
|
104330
|
+
transcript: graded.transcript,
|
|
104331
|
+
timestamp: graded.metadata.updatedAt
|
|
104332
|
+
}));
|
|
104333
|
+
this.reconcileTranscriptChildren(this.tokenContext(attempt), graded).catch((error88) => this.recordTranscriptFailure("checkpoint_projection", params.attemptId, error88));
|
|
104377
104334
|
return {
|
|
104378
|
-
attemptId:
|
|
104379
|
-
responseVersion:
|
|
104335
|
+
attemptId: params.attemptId,
|
|
104336
|
+
responseVersion: graded.metadata.responseVersion,
|
|
104380
104337
|
status: "in_progress",
|
|
104381
|
-
responses
|
|
104338
|
+
responses: graded.metadata.responses,
|
|
104339
|
+
transcript: graded.transcript
|
|
104382
104340
|
};
|
|
104383
104341
|
});
|
|
104384
104342
|
}
|
|
104385
|
-
async
|
|
104343
|
+
async recordProgress({
|
|
104386
104344
|
input,
|
|
104387
104345
|
...params
|
|
104388
104346
|
}) {
|
|
104389
104347
|
await this.deps.validateDeveloperAccess(params.user, params.gameId);
|
|
104390
|
-
|
|
104391
|
-
|
|
104392
|
-
|
|
104393
|
-
|
|
104394
|
-
}
|
|
104395
|
-
throw AssessmentRuntimeError.from(assessmentFlowViolation("routingNodeKey is not accepted by the hosted conventional item-submit flow.", { routingNodeKey: input.routingNodeKey }));
|
|
104396
|
-
}
|
|
104397
|
-
let preview = await this.prepareItemSubmission(params, input);
|
|
104398
|
-
let preparationAttempts = 1;
|
|
104399
|
-
while (true) {
|
|
104400
|
-
const committed = await this.deps.assessmentRuntimeLock(this.deps.db, [assessmentRuntimeAttemptLockKey(params.attemptId)], async (db2) => {
|
|
104401
|
-
const { attempt, enrollment } = await this.resolveAttemptAuthorization(params, {
|
|
104402
|
-
db: db2,
|
|
104403
|
-
developerAccessValidated: true
|
|
104404
|
-
});
|
|
104405
|
-
if (this.isPlatformRoutedDiagnosticMetadata(attempt.metadata)) {
|
|
104406
|
-
this.requireActiveAttempt({ attempt, enrollment }, params.attemptId);
|
|
104407
|
-
throw AssessmentRuntimeError.from(assessmentFlowViolation("Platform-routed diagnostic submissions require routingNodeKey.", { flow: "platform-routed-item-submit" }));
|
|
104408
|
-
}
|
|
104409
|
-
const replay = resolveAssessmentItemReplay(attempt.metadata, input);
|
|
104410
|
-
if (replay.action === "conflict") {
|
|
104411
|
-
this.requireActiveAttempt({ attempt, enrollment }, params.attemptId);
|
|
104412
|
-
throw AssessmentRuntimeError.from(replay.failure);
|
|
104413
|
-
}
|
|
104414
|
-
if (replay.action === "replay") {
|
|
104415
|
-
const prior = replay.submission;
|
|
104416
|
-
const child2 = attempt.metadata.purpose === "review" ? await this.prepareReviewAssessmentChildResult({
|
|
104417
|
-
attempt,
|
|
104418
|
-
metadata: attempt.metadata,
|
|
104419
|
-
submission: prior
|
|
104420
|
-
}) : null;
|
|
104421
|
-
return {
|
|
104422
|
-
action: "complete",
|
|
104423
|
-
response: buildAssessmentItemReplayResult(attempt.result.sourcedId, attempt.metadata, prior),
|
|
104424
|
-
checkpoint: child2,
|
|
104425
|
-
projection: attempt.metadata.purpose === "review" ? {
|
|
104426
|
-
result: attempt.result,
|
|
104427
|
-
metadata: attempt.metadata,
|
|
104428
|
-
itemIdentifier: input.itemIdentifier,
|
|
104429
|
-
integration: attempt.integration
|
|
104430
|
-
} : null
|
|
104431
|
-
};
|
|
104432
|
-
}
|
|
104433
|
-
this.requireActiveAttempt({ attempt, enrollment }, params.attemptId);
|
|
104434
|
-
this.assertInProgress(attempt.result);
|
|
104435
|
-
if (!preview || preview.responseVersion !== attempt.metadata.responseVersion || preview.submissionId !== input.submissionId || preview.itemIdentifier !== input.itemIdentifier) {
|
|
104436
|
-
if (preparationAttempts >= TimebackAssessmentRuntimeService.ITEM_SUBMISSION_MAX_PREPARATION_ATTEMPTS) {
|
|
104437
|
-
throw assessmentPreparationContentionError(input.expectedResponseVersion, attempt.metadata.responseVersion, "assessment");
|
|
104438
|
-
}
|
|
104439
|
-
return { action: "prepare" };
|
|
104440
|
-
}
|
|
104441
|
-
const transition = prepareAssessmentItemSubmission({
|
|
104442
|
-
purpose: attempt.metadata.purpose,
|
|
104443
|
-
responseVersion: attempt.metadata.responseVersion,
|
|
104444
|
-
responses: attempt.metadata.responses,
|
|
104445
|
-
itemSubmissions: attempt.metadata.itemSubmissions,
|
|
104446
|
-
assessment: preview.assessment
|
|
104447
|
-
}, input);
|
|
104448
|
-
if (transition.action === "reject") {
|
|
104449
|
-
throw AssessmentRuntimeError.from(transition.failure);
|
|
104450
|
-
}
|
|
104451
|
-
if (transition.action === "replay") {
|
|
104452
|
-
const child2 = attempt.metadata.purpose === "review" ? await this.prepareReviewAssessmentChildResult({
|
|
104453
|
-
attempt,
|
|
104454
|
-
metadata: attempt.metadata,
|
|
104455
|
-
submission: transition.submission
|
|
104456
|
-
}) : null;
|
|
104457
|
-
return {
|
|
104458
|
-
action: "complete",
|
|
104459
|
-
response: {
|
|
104460
|
-
attemptId: attempt.result.sourcedId,
|
|
104461
|
-
responseVersion: transition.responseVersion,
|
|
104462
|
-
status: "in_progress",
|
|
104463
|
-
responses: transition.responses,
|
|
104464
|
-
itemSubmissions: attempt.metadata.itemSubmissions,
|
|
104465
|
-
submission: transition.submission
|
|
104466
|
-
},
|
|
104467
|
-
checkpoint: child2,
|
|
104468
|
-
projection: attempt.metadata.purpose === "review" ? {
|
|
104469
|
-
result: attempt.result,
|
|
104470
|
-
metadata: attempt.metadata,
|
|
104471
|
-
itemIdentifier: input.itemIdentifier,
|
|
104472
|
-
integration: attempt.integration
|
|
104473
|
-
} : null
|
|
104474
|
-
};
|
|
104475
|
-
}
|
|
104476
|
-
if (!preview.scoring) {
|
|
104477
|
-
throw new Error("Prepared item submission is missing its score");
|
|
104478
|
-
}
|
|
104479
|
-
const submittedAt = new Date().toISOString();
|
|
104480
|
-
const completed = completeAssessmentItemSubmission(attempt.metadata, input, transition, preview.scoring, submittedAt);
|
|
104481
|
-
const metadata2 = {
|
|
104482
|
-
...attempt.metadata,
|
|
104483
|
-
responses: completed.responses,
|
|
104484
|
-
responseVersion: completed.responseVersion,
|
|
104485
|
-
itemSubmissions: completed.itemSubmissions,
|
|
104486
|
-
updatedAt: submittedAt
|
|
104487
|
-
};
|
|
104488
|
-
const child = metadata2.purpose === "review" ? await this.prepareReviewAssessmentChildResult({
|
|
104489
|
-
attempt,
|
|
104490
|
-
metadata: metadata2,
|
|
104491
|
-
submission: completed.submission
|
|
104492
|
-
}) : null;
|
|
104493
|
-
await this.putResultMetadata(attempt.result, metadata2);
|
|
104494
|
-
return {
|
|
104495
|
-
action: "complete",
|
|
104496
|
-
response: {
|
|
104497
|
-
attemptId: attempt.result.sourcedId,
|
|
104498
|
-
responseVersion: completed.responseVersion,
|
|
104499
|
-
status: "in_progress",
|
|
104500
|
-
responses: completed.responses,
|
|
104501
|
-
itemSubmissions: completed.itemSubmissions,
|
|
104502
|
-
submission: completed.submission
|
|
104503
|
-
},
|
|
104504
|
-
checkpoint: child,
|
|
104505
|
-
projection: metadata2.purpose === "review" ? {
|
|
104506
|
-
result: attempt.result,
|
|
104507
|
-
metadata: metadata2,
|
|
104508
|
-
itemIdentifier: input.itemIdentifier,
|
|
104509
|
-
integration: attempt.integration
|
|
104510
|
-
} : null
|
|
104511
|
-
};
|
|
104512
|
-
});
|
|
104513
|
-
if (committed.action === "prepare") {
|
|
104514
|
-
preparationAttempts += 1;
|
|
104515
|
-
preview = await this.scoreItemSubmission(params, input);
|
|
104516
|
-
} else {
|
|
104517
|
-
if (committed.projection) {
|
|
104518
|
-
this.projectReviewItemResponse(params, committed.projection, committed.checkpoint);
|
|
104519
|
-
}
|
|
104520
|
-
return committed.response;
|
|
104521
|
-
}
|
|
104522
|
-
}
|
|
104523
|
-
}
|
|
104524
|
-
async submitDiagnosticItem(params, input) {
|
|
104525
|
-
let preview = await this.prepareDiagnosticItemSubmission(params, input);
|
|
104526
|
-
let preparationAttempts = 1;
|
|
104527
|
-
while (true) {
|
|
104528
|
-
const committed = await this.deps.assessmentRuntimeLock(this.deps.db, [assessmentRuntimeAttemptLockKey(params.attemptId)], async (db2) => {
|
|
104529
|
-
const { attempt, enrollment } = await this.resolveAttemptAuthorization(params, { db: db2, developerAccessValidated: true });
|
|
104530
|
-
if (!this.isPlatformRoutedDiagnosticMetadata(attempt.metadata)) {
|
|
104531
|
-
this.requireActiveAttempt({ attempt, enrollment }, params.attemptId);
|
|
104532
|
-
throw AssessmentRuntimeError.from(assessmentFlowViolation("routingNodeKey is accepted only for a platform-routed diagnostic.", { routingNodeKey: input.routingNodeKey }));
|
|
104533
|
-
}
|
|
104534
|
-
const metadata2 = attempt.metadata;
|
|
104535
|
-
const replay = resolveDiagnosticItemReplay(metadata2, input);
|
|
104536
|
-
if (replay.action === "conflict") {
|
|
104537
|
-
this.requireActiveAttempt({ attempt, enrollment }, params.attemptId);
|
|
104538
|
-
throw AssessmentRuntimeError.from(replay.failure);
|
|
104539
|
-
}
|
|
104540
|
-
if (replay.action === "replay") {
|
|
104541
|
-
const { submission: prior, ledgerEntry, priorIndex } = replay;
|
|
104542
|
-
let routing = replay.routing;
|
|
104543
|
-
if (!routing) {
|
|
104544
|
-
const assessment = preview?.assessment ?? await this.loadAttemptAssessment(metadata2);
|
|
104545
|
-
const loaded2 = await this.loadAttemptDiagnosticRouting(metadata2, assessment, db2);
|
|
104546
|
-
const replayed = replayDiagnosticRouting(loaded2.manifest, metadata2.diagnostic.ledger.slice(0, priorIndex + 1));
|
|
104547
|
-
if (!replayed.ok) {
|
|
104548
|
-
throw AssessmentRuntimeError.from(assessmentFlowViolation("The committed diagnostic routing state cannot be replayed.", { failure: replayed.failure }));
|
|
104549
|
-
}
|
|
104550
|
-
routing = this.diagnosticRoutingSnapshot(metadata2, replayed.state);
|
|
104551
|
-
}
|
|
104552
|
-
const child2 = await this.prepareDiagnosticAssessmentChildResult({
|
|
104553
|
-
attempt,
|
|
104554
|
-
metadata: metadata2,
|
|
104555
|
-
submission: prior,
|
|
104556
|
-
transition: ledgerEntry
|
|
104557
|
-
});
|
|
104558
|
-
return {
|
|
104559
|
-
action: "complete",
|
|
104560
|
-
response: buildDiagnosticItemReplayResult(attempt.result.sourcedId, prior, ledgerEntry, routing),
|
|
104561
|
-
checkpoint: child2,
|
|
104562
|
-
projection: {
|
|
104563
|
-
result: attempt.result,
|
|
104564
|
-
metadata: metadata2,
|
|
104565
|
-
ledgerEntry,
|
|
104566
|
-
integration: attempt.integration
|
|
104567
|
-
}
|
|
104568
|
-
};
|
|
104569
|
-
}
|
|
104570
|
-
this.requireActiveAttempt({ attempt, enrollment }, params.attemptId);
|
|
104571
|
-
this.assertInProgress(attempt.result);
|
|
104572
|
-
if (!preview || preview.responseVersion !== metadata2.responseVersion || preview.submissionId !== input.submissionId || preview.routingNodeKey !== input.routingNodeKey || preview.itemIdentifier !== input.itemIdentifier) {
|
|
104573
|
-
if (preparationAttempts >= TimebackAssessmentRuntimeService.ITEM_SUBMISSION_MAX_PREPARATION_ATTEMPTS) {
|
|
104574
|
-
throw assessmentPreparationContentionError(input.expectedResponseVersion, metadata2.responseVersion, "diagnostic");
|
|
104575
|
-
}
|
|
104576
|
-
return { action: "prepare" };
|
|
104577
|
-
}
|
|
104578
|
-
this.assertResponseVersion(metadata2, input.expectedResponseVersion);
|
|
104579
|
-
const loaded = await this.loadAttemptDiagnosticRouting(metadata2, preview.assessment, db2);
|
|
104580
|
-
const expected = loaded.state.next;
|
|
104581
|
-
if (loaded.state.status !== "in-progress" || !expected || expected.nodeKey !== input.routingNodeKey || expected.itemIdentifier !== input.itemIdentifier) {
|
|
104582
|
-
throw AssessmentRuntimeError.from(assessmentFlowViolation(expected ? "Submitted diagnostic item is not the canonical next item." : "Diagnostic routing is already ready to complete.", {
|
|
104583
|
-
expectedRoutingNodeKey: expected?.nodeKey ?? null,
|
|
104584
|
-
routingNodeKey: input.routingNodeKey,
|
|
104585
|
-
expectedItemIdentifier: expected?.itemIdentifier ?? null,
|
|
104586
|
-
itemIdentifier: input.itemIdentifier
|
|
104587
|
-
}));
|
|
104588
|
-
}
|
|
104589
|
-
const responses = prepareDiagnosticAssessmentResponses(preview.assessment, metadata2.responses, input);
|
|
104590
|
-
const scoring = preview.scoring;
|
|
104591
|
-
if (!scoring || typeof scoring.isCorrect !== "boolean") {
|
|
104592
|
-
throw AssessmentRuntimeError.from(assessmentFlowViolation(`Diagnostic item ${input.itemIdentifier} did not produce determinate binary grading.`, { itemIdentifier: input.itemIdentifier }));
|
|
104593
|
-
}
|
|
104594
|
-
const advanced = advanceDiagnosticRouting(loaded.manifest, metadata2.diagnostic.ledger, {
|
|
104595
|
-
routingNodeKey: input.routingNodeKey,
|
|
104596
|
-
itemIdentifier: input.itemIdentifier,
|
|
104597
|
-
isCorrect: scoring.isCorrect
|
|
104598
|
-
});
|
|
104599
|
-
if (!advanced.ok) {
|
|
104600
|
-
throw AssessmentRuntimeError.from(assessmentFlowViolation("The diagnostic routing transition was rejected.", { failure: advanced.failure }));
|
|
104601
|
-
}
|
|
104602
|
-
const submittedAt = new Date().toISOString();
|
|
104603
|
-
const responseVersion = metadata2.responseVersion + 1;
|
|
104604
|
-
const routingSnapshot = this.diagnosticRoutingSnapshot(metadata2, advanced.state);
|
|
104605
|
-
const routingSnapshots = metadata2.diagnostic.routingSnapshots ? [...metadata2.diagnostic.routingSnapshots, routingSnapshot] : undefined;
|
|
104606
|
-
const submission = {
|
|
104607
|
-
submissionId: input.submissionId,
|
|
104608
|
-
itemIdentifier: input.itemIdentifier,
|
|
104609
|
-
submittedAt,
|
|
104610
|
-
responseVersion,
|
|
104611
|
-
answered: Object.keys(responses[input.itemIdentifier] ?? {}).length > 0,
|
|
104612
|
-
score: scoring.score,
|
|
104613
|
-
isCorrect: scoring.isCorrect,
|
|
104614
|
-
grading: scoring.grading
|
|
104615
|
-
};
|
|
104616
|
-
const nextMetadata = {
|
|
104617
|
-
...metadata2,
|
|
104618
|
-
responses,
|
|
104619
|
-
responseVersion,
|
|
104620
|
-
itemSubmissions: [...metadata2.itemSubmissions, submission],
|
|
104621
|
-
updatedAt: submittedAt,
|
|
104622
|
-
diagnostic: {
|
|
104623
|
-
...metadata2.diagnostic,
|
|
104624
|
-
ledger: [...metadata2.diagnostic.ledger, advanced.ledgerEntry],
|
|
104625
|
-
...routingSnapshots ? { routingSnapshots } : {},
|
|
104626
|
-
state: advanced.state
|
|
104627
|
-
}
|
|
104628
|
-
};
|
|
104629
|
-
const child = await this.prepareDiagnosticAssessmentChildResult({
|
|
104630
|
-
attempt,
|
|
104631
|
-
metadata: nextMetadata,
|
|
104632
|
-
submission,
|
|
104633
|
-
transition: advanced.ledgerEntry
|
|
104634
|
-
});
|
|
104635
|
-
await this.putResultMetadata(attempt.result, nextMetadata);
|
|
104636
|
-
return {
|
|
104637
|
-
action: "complete",
|
|
104638
|
-
response: {
|
|
104639
|
-
attemptId: attempt.result.sourcedId,
|
|
104640
|
-
responseVersion,
|
|
104641
|
-
status: "in_progress",
|
|
104642
|
-
routing: routingSnapshot,
|
|
104643
|
-
submission: diagnosticReceipt(submission, advanced.ledgerEntry)
|
|
104644
|
-
},
|
|
104645
|
-
checkpoint: child,
|
|
104646
|
-
projection: {
|
|
104647
|
-
result: attempt.result,
|
|
104648
|
-
metadata: nextMetadata,
|
|
104649
|
-
ledgerEntry: advanced.ledgerEntry,
|
|
104650
|
-
integration: attempt.integration
|
|
104651
|
-
}
|
|
104652
|
-
};
|
|
104653
|
-
});
|
|
104654
|
-
if (committed.action === "prepare") {
|
|
104655
|
-
preparationAttempts += 1;
|
|
104656
|
-
preview = await this.scoreDiagnosticItemSubmission(params, input);
|
|
104657
|
-
} else {
|
|
104658
|
-
this.projectDiagnosticItemResponse(committed.projection, preview?.assessment, committed.checkpoint);
|
|
104659
|
-
return committed.response;
|
|
104660
|
-
}
|
|
104661
|
-
}
|
|
104348
|
+
const { context: context2, transcript, submissionId } = prepareAssessmentProgress(await this.deps.verifyAssessmentToken(input.assessmentToken), params, input);
|
|
104349
|
+
const graded = await this.gradeTranscript(context2, transcript);
|
|
104350
|
+
await this.reconcileTranscriptChildren(context2, graded, new Set([submissionId]));
|
|
104351
|
+
return { attemptId: params.attemptId };
|
|
104662
104352
|
}
|
|
104663
|
-
async
|
|
104353
|
+
async submitItem({
|
|
104354
|
+
input,
|
|
104355
|
+
...params
|
|
104356
|
+
}) {
|
|
104357
|
+
await this.deps.validateDeveloperAccess(params.user, params.gameId);
|
|
104358
|
+
const context2 = requireAssessmentFeedbackContext(await this.deps.verifyAssessmentToken(input.assessmentToken), params);
|
|
104359
|
+
const transcript = parseAssessmentTranscript(input.transcript);
|
|
104360
|
+
const assessment = await this.loadAttemptAssessment(context2.metadata);
|
|
104361
|
+
const appended = prepareAssessmentItemTranscript(assessment, { ...input, transcript });
|
|
104362
|
+
const graded = await this.gradeTranscript(context2, appended.transcript);
|
|
104363
|
+
const submission = graded.metadata.itemSubmissions[appended.index];
|
|
104364
|
+
this.reconcileTranscriptChildren(context2, graded, new Set([submission.submissionId])).catch((error88) => this.recordTranscriptFailure("child_projection", params.attemptId, error88));
|
|
104365
|
+
return buildAssessmentTranscriptFeedback(params.attemptId, graded, appended.index);
|
|
104366
|
+
}
|
|
104367
|
+
async gradeTranscript(context2, transcript, terminal = false, db2 = this.deps.db) {
|
|
104368
|
+
const startedAt = Date.now();
|
|
104664
104369
|
try {
|
|
104665
|
-
|
|
104666
|
-
|
|
104667
|
-
|
|
104668
|
-
|
|
104669
|
-
|
|
104670
|
-
|
|
104671
|
-
|
|
104672
|
-
|
|
104673
|
-
|
|
104674
|
-
|
|
104675
|
-
|
|
104676
|
-
|
|
104677
|
-
|
|
104678
|
-
|
|
104679
|
-
|
|
104680
|
-
|
|
104681
|
-
|
|
104682
|
-
|
|
104683
|
-
|
|
104684
|
-
|
|
104685
|
-
const artifactIdentity = assessmentArtifactIdentityFromAttempt(attempt.metadata);
|
|
104686
|
-
const scoringArtifact = await this.artifactLoader.loadMatchedScoring(assessment, artifactIdentity);
|
|
104687
|
-
const loaded = await this.loadAttemptDiagnosticRouting(attempt.metadata, assessment, this.deps.db, scoringArtifact);
|
|
104688
|
-
const expected = loaded.state.next;
|
|
104689
|
-
let scoring = null;
|
|
104690
|
-
if (loaded.state.status === "in-progress" && expected?.nodeKey === input.routingNodeKey && expected.itemIdentifier === input.itemIdentifier && attempt.metadata.responseVersion === input.expectedResponseVersion) {
|
|
104691
|
-
const responses = prepareDiagnosticAssessmentResponses(assessment, attempt.metadata.responses, input);
|
|
104692
|
-
scoring = await this.scoreItem({
|
|
104370
|
+
const assessment = await this.loadAttemptAssessment(context2.metadata);
|
|
104371
|
+
const prepared = prepareAttemptAssessmentTranscript(context2, assessment, transcript, terminal);
|
|
104372
|
+
const artifactIdentity = assessmentArtifactIdentityFromAttempt(context2.metadata);
|
|
104373
|
+
const artifact = await this.artifactLoader.loadMatchedScoring(assessment, artifactIdentity);
|
|
104374
|
+
const [itemResults, routing] = await Promise.all([
|
|
104375
|
+
scoreCachedAssessmentItems({
|
|
104376
|
+
assessment,
|
|
104377
|
+
responses: prepared.responses,
|
|
104378
|
+
artifactIdentity,
|
|
104379
|
+
purpose: context2.metadata.purpose,
|
|
104380
|
+
itemIdentifiers: new Set(prepared.transcript.administrations.map((entry) => entry.itemIdentifier)),
|
|
104381
|
+
artifact,
|
|
104382
|
+
cache: this.itemScoreCache,
|
|
104383
|
+
processResponse: (identifier, request) => this.requireClient().qtiApi.assessmentItems.processResponse(identifier, request),
|
|
104384
|
+
onEvent: addEvent
|
|
104385
|
+
}),
|
|
104386
|
+
this.isPlatformRoutedDiagnosticMetadata(context2.metadata) ? this.loadAttemptDiagnosticRouting(context2.metadata, assessment, db2, artifact) : undefined
|
|
104387
|
+
]);
|
|
104388
|
+
const graded = buildGradedAssessmentTranscript({
|
|
104389
|
+
context: context2,
|
|
104693
104390
|
assessment,
|
|
104694
|
-
|
|
104695
|
-
|
|
104696
|
-
|
|
104697
|
-
|
|
104698
|
-
|
|
104391
|
+
prepared,
|
|
104392
|
+
itemResults,
|
|
104393
|
+
submittedAt: new Date().toISOString(),
|
|
104394
|
+
manifest: routing?.manifest,
|
|
104395
|
+
terminal
|
|
104396
|
+
});
|
|
104397
|
+
addEvent("assessment.transcript_validated", {
|
|
104398
|
+
"app.assessment.attempt_id": context2.attemptId,
|
|
104399
|
+
"app.assessment.administration_count": graded.metadata.responseVersion,
|
|
104400
|
+
"app.assessment.duration_ms": Date.now() - startedAt
|
|
104699
104401
|
});
|
|
104700
|
-
|
|
104701
|
-
return {
|
|
104702
|
-
responseVersion: attempt.metadata.responseVersion,
|
|
104703
|
-
submissionId: input.submissionId,
|
|
104704
|
-
routingNodeKey: input.routingNodeKey,
|
|
104705
|
-
itemIdentifier: input.itemIdentifier,
|
|
104706
|
-
assessment,
|
|
104707
|
-
scoring
|
|
104708
|
-
};
|
|
104709
|
-
}
|
|
104710
|
-
async scoreItems({
|
|
104711
|
-
preparedArtifact,
|
|
104712
|
-
...input
|
|
104713
|
-
}) {
|
|
104714
|
-
const artifact = preparedArtifact === undefined ? await this.artifactLoader.loadMatchedScoring(input.assessment, input.artifactIdentity) : preparedArtifact;
|
|
104715
|
-
return scoreAssessmentItems({
|
|
104716
|
-
...input,
|
|
104717
|
-
artifact,
|
|
104718
|
-
processResponse: (identifier, request) => this.requireClient().qtiApi.assessmentItems.processResponse(identifier, request),
|
|
104719
|
-
onEvent: addEvent
|
|
104720
|
-
});
|
|
104721
|
-
}
|
|
104722
|
-
async scoreItem(input) {
|
|
104723
|
-
const [scoring] = await this.scoreItems({
|
|
104724
|
-
assessment: input.assessment,
|
|
104725
|
-
responses: input.responses,
|
|
104726
|
-
artifactIdentity: input.artifactIdentity,
|
|
104727
|
-
purpose: input.purpose,
|
|
104728
|
-
itemIdentifiers: new Set([input.itemIdentifier]),
|
|
104729
|
-
preparedArtifact: input.preparedArtifact
|
|
104730
|
-
});
|
|
104731
|
-
return scoring;
|
|
104732
|
-
}
|
|
104733
|
-
async prepareItemSubmission(params, input) {
|
|
104734
|
-
try {
|
|
104735
|
-
return await this.scoreItemSubmission(params, input);
|
|
104402
|
+
return graded;
|
|
104736
104403
|
} catch (error88) {
|
|
104737
|
-
|
|
104738
|
-
"app.assessment.attempt_id": params.attemptId,
|
|
104739
|
-
"exception.type": errorType(error88),
|
|
104740
|
-
"app.error.message": errorMessage(error88)
|
|
104741
|
-
});
|
|
104742
|
-
return null;
|
|
104404
|
+
throw assessmentTranscriptRuntimeError(error88);
|
|
104743
104405
|
}
|
|
104744
104406
|
}
|
|
104745
|
-
|
|
104746
|
-
|
|
104747
|
-
|
|
104748
|
-
|
|
104749
|
-
|
|
104750
|
-
|
|
104751
|
-
const assessment = await this.loadAttemptAssessment(attempt.metadata);
|
|
104752
|
-
const transition = prepareAssessmentItemSubmission({
|
|
104753
|
-
purpose: attempt.metadata.purpose,
|
|
104754
|
-
responseVersion: attempt.metadata.responseVersion,
|
|
104755
|
-
responses: attempt.metadata.responses,
|
|
104756
|
-
itemSubmissions: attempt.metadata.itemSubmissions,
|
|
104757
|
-
assessment
|
|
104758
|
-
}, input);
|
|
104759
|
-
const scoring = transition.action === "commit" ? await this.scoreItem({
|
|
104760
|
-
assessment,
|
|
104761
|
-
responses: transition.responses,
|
|
104762
|
-
itemIdentifier: input.itemIdentifier,
|
|
104763
|
-
artifactIdentity: assessmentArtifactIdentityFromAttempt(attempt.metadata),
|
|
104764
|
-
purpose: attempt.metadata.purpose
|
|
104765
|
-
}) : null;
|
|
104766
|
-
return {
|
|
104767
|
-
responseVersion: attempt.metadata.responseVersion,
|
|
104768
|
-
submissionId: input.submissionId,
|
|
104769
|
-
itemIdentifier: input.itemIdentifier,
|
|
104770
|
-
assessment,
|
|
104771
|
-
scoring
|
|
104772
|
-
};
|
|
104773
|
-
}
|
|
104774
|
-
async scoreSubmission(attempt) {
|
|
104775
|
-
const assessment = await this.loadAttemptAssessment(attempt.metadata);
|
|
104776
|
-
validateAssessmentResponses(assessment, attempt.metadata.responses);
|
|
104777
|
-
const itemResults = attempt.metadata.purpose === "review" ? committedReviewItemResults(attempt.metadata, assessment) : await this.scoreItems({
|
|
104778
|
-
assessment,
|
|
104779
|
-
responses: attempt.metadata.responses,
|
|
104780
|
-
artifactIdentity: assessmentArtifactIdentityFromAttempt(attempt.metadata),
|
|
104781
|
-
purpose: attempt.metadata.purpose
|
|
104782
|
-
});
|
|
104783
|
-
return {
|
|
104784
|
-
responseVersion: attempt.metadata.responseVersion,
|
|
104785
|
-
assessment,
|
|
104786
|
-
itemResults,
|
|
104787
|
-
score: aggregateAssessmentScore(itemResults.map((result) => result.score.earned), itemResults.reduce((total, result) => total + result.score.possible, 0)),
|
|
104788
|
-
correctQuestions: countCorrectQuestions(itemResults.map((result) => result.isCorrect))
|
|
104789
|
-
};
|
|
104790
|
-
}
|
|
104791
|
-
async peekAttempt({
|
|
104792
|
-
studentId,
|
|
104793
|
-
attemptId
|
|
104794
|
-
}) {
|
|
104795
|
-
const result = await this.requireClient().api.oneroster.assessmentResults.get(attemptId).catch((error88) => {
|
|
104796
|
-
if (isApiError(error88) && error88.statusCode === 404) {
|
|
104797
|
-
throw this.unauthorizedAttempt(attemptId);
|
|
104798
|
-
}
|
|
104799
|
-
throw error88;
|
|
104407
|
+
recordTranscriptFailure(operation, attemptId, error88) {
|
|
104408
|
+
addEvent("assessment.transcript_operation_failed", {
|
|
104409
|
+
"app.assessment.operation": operation,
|
|
104410
|
+
"app.assessment.attempt_id": attemptId,
|
|
104411
|
+
"exception.type": errorType(error88),
|
|
104412
|
+
"app.error.message": errorMessage(error88)
|
|
104800
104413
|
});
|
|
104801
|
-
const metadata2 = playcademyAssessmentResultMetadata(result.metadata);
|
|
104802
|
-
if (!metadata2 || result.student.sourcedId !== studentId || result.status !== ONEROSTER_STATUS2.active) {
|
|
104803
|
-
throw this.unauthorizedAttempt(attemptId);
|
|
104804
|
-
}
|
|
104805
|
-
return { result, metadata: metadata2 };
|
|
104806
|
-
}
|
|
104807
|
-
async prepareSubmission(params, input) {
|
|
104808
|
-
try {
|
|
104809
|
-
const attempt = await this.peekAttempt(params);
|
|
104810
|
-
const disposition = classifyAssessmentSubmission({
|
|
104811
|
-
inProgress: attempt.result.inProgress ?? "",
|
|
104812
|
-
scoreStatus: attempt.result.scoreStatus,
|
|
104813
|
-
submissionId: attempt.metadata.submissionId
|
|
104814
|
-
}, input.submissionId);
|
|
104815
|
-
if (disposition !== "submit") {
|
|
104816
|
-
return null;
|
|
104817
|
-
}
|
|
104818
|
-
if (this.isPlatformRoutedDiagnosticMetadata(attempt.metadata)) {
|
|
104819
|
-
return null;
|
|
104820
|
-
}
|
|
104821
|
-
return await this.scoreSubmission(attempt);
|
|
104822
|
-
} catch (error88) {
|
|
104823
|
-
addEvent("assessment.submission_preview_failed", {
|
|
104824
|
-
"app.assessment.attempt_id": params.attemptId,
|
|
104825
|
-
"exception.type": errorType(error88),
|
|
104826
|
-
"app.error.message": errorMessage(error88)
|
|
104827
|
-
});
|
|
104828
|
-
return null;
|
|
104829
|
-
}
|
|
104830
104414
|
}
|
|
104831
104415
|
async submit({
|
|
104832
104416
|
input,
|
|
@@ -104834,121 +104418,83 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
104834
104418
|
...params
|
|
104835
104419
|
}) {
|
|
104836
104420
|
await this.deps.validateDeveloperAccess(params.user, params.gameId);
|
|
104837
|
-
const
|
|
104838
|
-
const
|
|
104839
|
-
|
|
104840
|
-
|
|
104841
|
-
|
|
104842
|
-
|
|
104843
|
-
|
|
104844
|
-
|
|
104845
|
-
|
|
104421
|
+
const transcript = parseAssessmentTranscript(input.transcript);
|
|
104422
|
+
const request = { ...input, transcript };
|
|
104423
|
+
const requestHash = await assessmentTranscriptRequestHash(request);
|
|
104424
|
+
const accepted = await this.deps.assessmentRuntimeLock(this.deps.db, [assessmentRuntimeAttemptLockKey(params.attemptId)], async (db2) => {
|
|
104425
|
+
const { attempt } = await this.resolveAttemptAuthorization(params, {
|
|
104426
|
+
db: db2,
|
|
104427
|
+
developerAccessValidated: true,
|
|
104428
|
+
includeEnrollment: false
|
|
104429
|
+
});
|
|
104430
|
+
const disposition = resolveAssessmentTranscriptSubmission(attempt.result, attempt.metadata, attempt.integration.status, request, requestHash);
|
|
104431
|
+
if (disposition === "replay") {
|
|
104846
104432
|
return {
|
|
104847
|
-
response: this.submittedResult(
|
|
104848
|
-
emission: this.replayCompletion(
|
|
104433
|
+
response: this.submittedResult(attempt.result, attempt.metadata),
|
|
104434
|
+
emission: this.replayCompletion(attempt, input.submissionId, params, game2)
|
|
104849
104435
|
};
|
|
104850
104436
|
}
|
|
104851
|
-
const
|
|
104852
|
-
|
|
104853
|
-
|
|
104854
|
-
}
|
|
104855
|
-
|
|
104856
|
-
|
|
104857
|
-
|
|
104858
|
-
|
|
104859
|
-
|
|
104860
|
-
|
|
104861
|
-
attempt: { ...attempt, metadata: attempt.metadata },
|
|
104862
|
-
input,
|
|
104863
|
-
params,
|
|
104864
|
-
game: game2,
|
|
104865
|
-
db: db2
|
|
104866
|
-
});
|
|
104867
|
-
}
|
|
104868
|
-
if (assessmentFlowForPurpose(attempt.metadata.purpose) === "item-submit") {
|
|
104869
|
-
const assessment2 = await this.loadAttemptAssessment(attempt.metadata);
|
|
104870
|
-
const flowFailure = assessmentCompletionFlowFailure(attempt.metadata.purpose, attempt.metadata.itemSubmissions, assessment2.items.length);
|
|
104871
|
-
if (flowFailure) {
|
|
104872
|
-
throw AssessmentRuntimeError.from(flowFailure);
|
|
104873
|
-
}
|
|
104874
|
-
}
|
|
104875
|
-
const scored = preview && preview.responseVersion === attempt.metadata.responseVersion ? preview : await this.scoreSubmission(attempt);
|
|
104876
|
-
const { assessment, itemResults, score, correctQuestions } = scored;
|
|
104877
|
-
const client2 = this.requireClient();
|
|
104437
|
+
const graded = await this.gradeTranscript({
|
|
104438
|
+
attemptId: params.attemptId,
|
|
104439
|
+
metadata: attempt.metadata
|
|
104440
|
+
}, transcript, true, db2);
|
|
104441
|
+
const diagnostic = this.isPlatformRoutedDiagnosticMetadata(graded.metadata) ? summarizeDiagnosticSubmission(graded.metadata, graded.manifest) : null;
|
|
104442
|
+
const itemOutcomes = graded.metadata.purpose === "review" ? assessmentReviewTranscriptOutcomes({
|
|
104443
|
+
metadata: graded.metadata,
|
|
104444
|
+
itemResults: graded.itemResults
|
|
104445
|
+
}) : undefined;
|
|
104446
|
+
const courseName = attempt.metadata.courseName ?? "Game Course";
|
|
104878
104447
|
const timestamp6 = new Date().toISOString();
|
|
104879
|
-
const reviewOutcomes = attempt.metadata.purpose === "review" ? this.reviewItemOutcomes({
|
|
104880
|
-
metadata: attempt.metadata,
|
|
104881
|
-
itemResults
|
|
104882
|
-
}) : null;
|
|
104883
104448
|
const finalization = buildAssessmentResultSubmission({
|
|
104884
104449
|
result: attempt.result,
|
|
104885
|
-
metadata:
|
|
104886
|
-
score,
|
|
104450
|
+
metadata: graded.metadata,
|
|
104451
|
+
score: graded.score,
|
|
104887
104452
|
submissionId: input.submissionId,
|
|
104888
104453
|
timestamp: timestamp6,
|
|
104889
|
-
testName: assessment.title,
|
|
104890
|
-
courseName
|
|
104891
|
-
totalQuestions: itemResults.length,
|
|
104892
|
-
correctQuestions,
|
|
104454
|
+
testName: graded.assessment.title,
|
|
104455
|
+
courseName,
|
|
104456
|
+
totalQuestions: graded.itemResults.length,
|
|
104457
|
+
correctQuestions: graded.correctQuestions,
|
|
104893
104458
|
...input.session ? { session: input.session } : {},
|
|
104894
|
-
...
|
|
104459
|
+
...itemOutcomes ? { itemOutcomes } : {},
|
|
104460
|
+
...diagnostic ? { diagnosticTracks: diagnostic.tracks } : {}
|
|
104895
104461
|
});
|
|
104896
|
-
const metadata2 =
|
|
104897
|
-
|
|
104898
|
-
|
|
104899
|
-
|
|
104900
|
-
|
|
104901
|
-
|
|
104902
|
-
|
|
104903
|
-
|
|
104462
|
+
const metadata2 = {
|
|
104463
|
+
...finalization.metadata,
|
|
104464
|
+
completion: { ...finalization.metadata.completion, requestHash }
|
|
104465
|
+
};
|
|
104466
|
+
const resultUpdate = {
|
|
104467
|
+
...finalization.resultUpdate,
|
|
104468
|
+
metadata: mergeAssessmentRuntimeMetadata(attempt.result.metadata, metadata2)
|
|
104469
|
+
};
|
|
104470
|
+
await this.requireClient().api.oneroster.assessmentResults.upsert(params.attemptId, resultUpdate);
|
|
104471
|
+
const result = { ...attempt.result, ...resultUpdate };
|
|
104472
|
+
const emission = {
|
|
104473
|
+
attempt: { ...attempt, result, metadata: metadata2 },
|
|
104474
|
+
testIdentifier: graded.assessment.identifier,
|
|
104475
|
+
testName: graded.assessment.title,
|
|
104476
|
+
score: graded.score,
|
|
104477
|
+
totalQuestions: graded.itemResults.length,
|
|
104478
|
+
correctQuestions: graded.correctQuestions,
|
|
104479
|
+
submissionId: input.submissionId,
|
|
104480
|
+
gameId: params.gameId,
|
|
104481
|
+
studentId: params.studentId,
|
|
104482
|
+
courseName,
|
|
104483
|
+
game: game2
|
|
104904
104484
|
};
|
|
104905
|
-
addEvent("assessment.awaiting_award_created", {
|
|
104906
|
-
"app.assessment.attempt_id": submittedForEmission.sourcedId,
|
|
104907
|
-
"app.assessment.submission_id": input.submissionId,
|
|
104908
|
-
"app.assessment.purpose": metadata2.purpose,
|
|
104909
|
-
"app.assessment.response_version": metadata2.responseVersion
|
|
104910
|
-
});
|
|
104911
104485
|
return {
|
|
104912
|
-
response:
|
|
104913
|
-
|
|
104914
|
-
|
|
104915
|
-
responseVersion: metadata2.responseVersion,
|
|
104916
|
-
status: "awaiting_award",
|
|
104917
|
-
responses: metadata2.responses,
|
|
104918
|
-
score,
|
|
104919
|
-
...metadata2.purpose === "review" ? {
|
|
104920
|
-
purpose: metadata2.purpose,
|
|
104921
|
-
itemOutcomes: reviewOutcomes
|
|
104922
|
-
} : { purpose: metadata2.purpose }
|
|
104923
|
-
},
|
|
104924
|
-
emission: {
|
|
104925
|
-
attempt: { ...attempt, result: submittedForEmission, metadata: metadata2 },
|
|
104926
|
-
testIdentifier: assessment.identifier,
|
|
104927
|
-
testName: assessment.title,
|
|
104928
|
-
score,
|
|
104929
|
-
totalQuestions: itemResults.length,
|
|
104930
|
-
correctQuestions,
|
|
104931
|
-
submissionId: input.submissionId,
|
|
104932
|
-
gameId: params.gameId,
|
|
104933
|
-
studentId: params.studentId,
|
|
104934
|
-
courseName: attempt.enrollment.course.title,
|
|
104935
|
-
game: game2
|
|
104936
|
-
}
|
|
104486
|
+
response: this.submittedResult(result, metadata2),
|
|
104487
|
+
emission,
|
|
104488
|
+
repair: { context: this.tokenContext(attempt), graded }
|
|
104937
104489
|
};
|
|
104938
104490
|
});
|
|
104939
|
-
if (
|
|
104940
|
-
|
|
104941
|
-
|
|
104942
|
-
|
|
104943
|
-
|
|
104944
|
-
const { result, metadata: metadata2 } = submission.emission.attempt;
|
|
104945
|
-
await Promise.allSettled([
|
|
104946
|
-
this.emitSessionBestEffort(submission.emission),
|
|
104947
|
-
...diagnosticCompletion ? [this.restoreDiagnosticChildResults(diagnosticCompletion)] : [],
|
|
104948
|
-
...metadata2.purpose === "review" ? [this.restoreAllCompletedReviewChildResponses(result, metadata2)] : []
|
|
104949
|
-
]);
|
|
104491
|
+
if (accepted.repair) {
|
|
104492
|
+
this.reconcileTranscriptChildren(accepted.repair.context, accepted.repair.graded).catch((error88) => this.recordTranscriptFailure("completion_repair", params.attemptId, error88));
|
|
104493
|
+
}
|
|
104494
|
+
if (accepted.emission) {
|
|
104495
|
+
this.emitSessionBestEffort(accepted.emission);
|
|
104950
104496
|
}
|
|
104951
|
-
return
|
|
104497
|
+
return accepted.response;
|
|
104952
104498
|
}
|
|
104953
104499
|
async finalize({
|
|
104954
104500
|
input,
|
|
@@ -105092,58 +104638,6 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
105092
104638
|
});
|
|
105093
104639
|
}
|
|
105094
104640
|
}
|
|
105095
|
-
async finalizeDiagnosticSubmission(input) {
|
|
105096
|
-
const { attempt } = input;
|
|
105097
|
-
const routing = await this.loadAttemptDiagnosticManifest(attempt.metadata, input.db);
|
|
105098
|
-
const { tracks, score, correctQuestions } = summarizeDiagnosticSubmission(attempt.metadata, routing.manifest);
|
|
105099
|
-
const timestamp6 = new Date().toISOString();
|
|
105100
|
-
const finalization = buildAssessmentResultSubmission({
|
|
105101
|
-
result: attempt.result,
|
|
105102
|
-
metadata: attempt.metadata,
|
|
105103
|
-
score,
|
|
105104
|
-
submissionId: input.input.submissionId,
|
|
105105
|
-
timestamp: timestamp6,
|
|
105106
|
-
testName: attempt.metadata.diagnostic.testName,
|
|
105107
|
-
courseName: attempt.enrollment.course.title,
|
|
105108
|
-
totalQuestions: attempt.metadata.itemSubmissions.length,
|
|
105109
|
-
correctQuestions,
|
|
105110
|
-
...input.input.session ? { session: input.input.session } : {},
|
|
105111
|
-
diagnosticTracks: tracks
|
|
105112
|
-
});
|
|
105113
|
-
const metadata2 = finalization.metadata;
|
|
105114
|
-
const submitted = await this.requireClient().api.oneroster.assessmentResults.upsert(attempt.result.sourcedId, finalization.resultUpdate);
|
|
105115
|
-
const submittedForEmission = {
|
|
105116
|
-
...submitted,
|
|
105117
|
-
score: score.normalized * 100,
|
|
105118
|
-
scoreDate: timestamp6,
|
|
105119
|
-
...ASSESSMENT_ATTEMPT_AWAITING_AWARD,
|
|
105120
|
-
metadata: finalization.resultUpdate.metadata
|
|
105121
|
-
};
|
|
105122
|
-
return {
|
|
105123
|
-
response: {
|
|
105124
|
-
purpose: "diagnostic",
|
|
105125
|
-
attemptId: submittedForEmission.sourcedId,
|
|
105126
|
-
submissionId: input.input.submissionId,
|
|
105127
|
-
diagnosticKey: metadata2.diagnostic.diagnosticKey,
|
|
105128
|
-
responseVersion: metadata2.responseVersion,
|
|
105129
|
-
status: "awaiting_award",
|
|
105130
|
-
tracks
|
|
105131
|
-
},
|
|
105132
|
-
emission: {
|
|
105133
|
-
attempt: { ...attempt, result: submittedForEmission, metadata: metadata2 },
|
|
105134
|
-
testIdentifier: attempt.metadata.selectedTest.identifier,
|
|
105135
|
-
testName: attempt.metadata.diagnostic.testName,
|
|
105136
|
-
score,
|
|
105137
|
-
totalQuestions: attempt.metadata.itemSubmissions.length,
|
|
105138
|
-
correctQuestions,
|
|
105139
|
-
submissionId: input.input.submissionId,
|
|
105140
|
-
gameId: input.params.gameId,
|
|
105141
|
-
studentId: input.params.studentId,
|
|
105142
|
-
courseName: attempt.enrollment.course.title,
|
|
105143
|
-
game: input.game
|
|
105144
|
-
}
|
|
105145
|
-
};
|
|
105146
|
-
}
|
|
105147
104641
|
async exportFixture({
|
|
105148
104642
|
gameId,
|
|
105149
104643
|
user
|
|
@@ -105264,28 +104758,11 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
105264
104758
|
liveTestIntegrationIds: integrationIdsWithLiveTests
|
|
105265
104759
|
};
|
|
105266
104760
|
}
|
|
105267
|
-
async selectIntegration(candidates,
|
|
105268
|
-
const {
|
|
105269
|
-
rows: candidateRows,
|
|
105270
|
-
enrollmentByCourse,
|
|
105271
|
-
liveTestIntegrationIds: integrationIdsWithLiveTests
|
|
105272
|
-
} = candidates;
|
|
105273
|
-
const needsOpenAnnotation = candidateRows.length > 1 || candidateRows.some((row) => !integrationIdsWithLiveTests.has(row.id));
|
|
105274
|
-
const openAttemptScope = {
|
|
105275
|
-
studentId,
|
|
105276
|
-
activityId: input.activityId,
|
|
105277
|
-
purpose: input.purpose,
|
|
105278
|
-
enrollmentIdByCourse: new Map(candidateRows.map((row) => [row.courseId, enrollmentByCourse.get(row.courseId).id])),
|
|
105279
|
-
requestFingerprint: this.startRequestFingerprint(input)
|
|
105280
|
-
};
|
|
105281
|
-
let openAttemptCourseIds = new Set;
|
|
105282
|
-
if (needsOpenAnnotation) {
|
|
105283
|
-
openAttemptCourseIds = results ? this.openAttemptCourseIdsFromResults(results, openAttemptScope) : await this.openAttemptCourseIds(openAttemptScope);
|
|
105284
|
-
}
|
|
104761
|
+
async selectIntegration(candidates, input) {
|
|
104762
|
+
const { rows: candidateRows, enrollmentByCourse, liveTestIntegrationIds } = candidates;
|
|
105285
104763
|
const annotated = candidateRows.map((row) => ({
|
|
105286
104764
|
...row,
|
|
105287
|
-
hasLiveTests:
|
|
105288
|
-
hasOpenAttempt: openAttemptCourseIds.has(row.courseId)
|
|
104765
|
+
hasLiveTests: liveTestIntegrationIds.has(row.id)
|
|
105289
104766
|
}));
|
|
105290
104767
|
const selection = selectAssessmentCourseCandidate(annotated, Boolean(input.subject));
|
|
105291
104768
|
if (selection.outcome === "none") {
|
|
@@ -105314,9 +104791,6 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
105314
104791
|
standard: assessmentStandardForRow(row)
|
|
105315
104792
|
})).filter((test) => !matchesMasteryStandard || matchesMasteryStandard(test.standard));
|
|
105316
104793
|
}
|
|
105317
|
-
async openAttemptCourseIds(scope) {
|
|
105318
|
-
return this.openAttemptCourseIdsFromResults(await this.studentAssessmentResults(scope.studentId), scope);
|
|
105319
|
-
}
|
|
105320
104794
|
async studentAssessmentResults(studentId) {
|
|
105321
104795
|
const stream = this.requireClient().api.oneroster.assessmentResults.stream({
|
|
105322
104796
|
where: {
|
|
@@ -105330,29 +104804,37 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
105330
104804
|
}
|
|
105331
104805
|
return results;
|
|
105332
104806
|
}
|
|
105333
|
-
openAttemptCourseIdsFromResults(results, scope) {
|
|
105334
|
-
const courseIds = new Set;
|
|
105335
|
-
for (const result of results) {
|
|
105336
|
-
const metadata2 = playcademyAssessmentResultMetadata(result.metadata);
|
|
105337
|
-
if (metadata2 && metadata2.activityId === scope.activityId && metadata2.purpose === scope.purpose && this.attemptMatchesRequestFingerprint(metadata2, scope.requestFingerprint) && result.student.sourcedId === scope.studentId && metadata2.enrollmentId === scope.enrollmentIdByCourse.get(metadata2.courseId) && (this.isOpen(result) || this.isAwaitingAward(result))) {
|
|
105338
|
-
courseIds.add(metadata2.courseId);
|
|
105339
|
-
}
|
|
105340
|
-
}
|
|
105341
|
-
return courseIds;
|
|
105342
|
-
}
|
|
105343
104807
|
async listAttempts(scope) {
|
|
105344
104808
|
return this.attemptListing(await this.studentAssessmentResults(scope.studentId), scope);
|
|
105345
104809
|
}
|
|
105346
|
-
attemptListing(results, scope) {
|
|
104810
|
+
async attemptListing(results, scope) {
|
|
105347
104811
|
const attempts = new Map;
|
|
105348
104812
|
const reviewChildren = new Map;
|
|
105349
104813
|
for (const result of results) {
|
|
105350
104814
|
const metadata2 = playcademyAssessmentResultMetadata(result.metadata);
|
|
105351
|
-
|
|
105352
|
-
|
|
105353
|
-
|
|
104815
|
+
if (scope.purpose === "review") {
|
|
104816
|
+
const verified = await verifyAssessmentChildResultCommand(result.metadata);
|
|
104817
|
+
if (verified.ok && verified.command.payload.context.kind === "review") {
|
|
104818
|
+
const { command } = verified;
|
|
104819
|
+
const { attempt, administration } = command.payload;
|
|
104820
|
+
const parent = await reviewQuestionLineItemId(await deterministicUUID([
|
|
104821
|
+
"playcademy:review-bank-line-item:v1",
|
|
104822
|
+
scope.integrationId,
|
|
104823
|
+
scope.activityId,
|
|
104824
|
+
attempt.selectedTest.identifier,
|
|
104825
|
+
verified.command.payload.context.bankRevision
|
|
104826
|
+
].join("\x00")), administration.itemIdentifier);
|
|
104827
|
+
const projection = await classifyAssessmentChildResult({
|
|
104828
|
+
result,
|
|
104829
|
+
command,
|
|
104830
|
+
childLineItemId: parent
|
|
104831
|
+
});
|
|
104832
|
+
if (attempt.activityId === scope.activityId && attempt.integrationId === scope.integrationId && attempt.enrollmentId === scope.enrollmentId && attempt.courseId === scope.courseId && attempt.studentId === scope.studentId && result.sourcedId === await assessmentChildResultId(attempt.attemptId, administration.submissionId) && projection.outcome === "replay") {
|
|
104833
|
+
reviewChildren.set(result.sourcedId, command);
|
|
104834
|
+
}
|
|
104835
|
+
}
|
|
105354
104836
|
}
|
|
105355
|
-
if (metadata2 &&
|
|
104837
|
+
if (metadata2 && this.attemptInScope(result, metadata2, scope)) {
|
|
105356
104838
|
const selectedAssessmentKey = metadata2.selectedTest.assessmentKey ?? assessmentKeyFromManagedQtiIdentifier(metadata2.selectedTest.identifier);
|
|
105357
104839
|
if (selectedAssessmentKey) {
|
|
105358
104840
|
attempts.set(result.sourcedId, {
|
|
@@ -105361,12 +104843,9 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
105361
104843
|
selection: {
|
|
105362
104844
|
attemptId: result.sourcedId,
|
|
105363
104845
|
selectedAssessmentKey,
|
|
105364
|
-
selectedTestIdentifier: metadata2.selectedTest.identifier,
|
|
105365
104846
|
inProgress: result.inProgress ?? "",
|
|
105366
104847
|
scoreStatus: result.scoreStatus,
|
|
105367
|
-
scoreDate: result.scoreDate
|
|
105368
|
-
updatedAt: result.dateLastModified ?? metadata2.updatedAt,
|
|
105369
|
-
resumable: metadata2.enrollmentId === scope.enrollmentId
|
|
104848
|
+
scoreDate: result.scoreDate
|
|
105370
104849
|
}
|
|
105371
104850
|
});
|
|
105372
104851
|
}
|
|
@@ -105374,147 +104853,61 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
105374
104853
|
}
|
|
105375
104854
|
return { attempts, reviewChildren };
|
|
105376
104855
|
}
|
|
105377
|
-
|
|
105378
|
-
|
|
105379
|
-
return attempts;
|
|
105380
|
-
}
|
|
105381
|
-
return new Map([...attempts].filter(([, attempt]) => this.attemptMatchesRequestFingerprint(attempt.metadata, requestFingerprint)));
|
|
105382
|
-
}
|
|
105383
|
-
startRequestFingerprint(input) {
|
|
105384
|
-
if (input.purpose === "review") {
|
|
105385
|
-
return reviewRequestFingerprint(input, DEFAULT_REVIEW_SELECTION_POLICY);
|
|
105386
|
-
}
|
|
105387
|
-
if (input.purpose === "mastery") {
|
|
105388
|
-
return masteryRequestFingerprint(input);
|
|
105389
|
-
}
|
|
105390
|
-
return input.purpose === "diagnostic" ? input.diagnosticKey : undefined;
|
|
105391
|
-
}
|
|
105392
|
-
attemptMatchesRequestFingerprint(metadata2, requestFingerprint) {
|
|
105393
|
-
if (metadata2.purpose === "review") {
|
|
105394
|
-
return metadata2.review.requestFingerprint === requestFingerprint;
|
|
105395
|
-
}
|
|
105396
|
-
if (metadata2.purpose === "mastery") {
|
|
105397
|
-
return metadata2.mastery.requestFingerprint === requestFingerprint;
|
|
105398
|
-
}
|
|
105399
|
-
if (metadata2.purpose === "diagnostic" && requestFingerprint !== undefined) {
|
|
105400
|
-
return this.isPlatformRoutedDiagnosticMetadata(metadata2) && metadata2.diagnostic.diagnosticKey === requestFingerprint;
|
|
105401
|
-
}
|
|
105402
|
-
return true;
|
|
105403
|
-
}
|
|
105404
|
-
reviewExposures(results, scope) {
|
|
105405
|
-
const exposures = [];
|
|
105406
|
-
for (const result of results) {
|
|
105407
|
-
const metadata2 = playcademyAssessmentItemResultMetadata(result.metadata);
|
|
105408
|
-
if (metadata2?.bankRevision === scope.bankRevision) {
|
|
105409
|
-
exposures.push({
|
|
105410
|
-
itemIdentifier: metadata2.itemIdentifier,
|
|
105411
|
-
deliveredAt: metadata2.deliveredAt
|
|
105412
|
-
});
|
|
105413
|
-
}
|
|
105414
|
-
}
|
|
105415
|
-
return exposures;
|
|
105416
|
-
}
|
|
105417
|
-
requireDecision(decision, purpose) {
|
|
104856
|
+
selectFreshStart(tests, listing, purpose) {
|
|
104857
|
+
const decision = selectRuntimeAssessment(tests, [...listing.attempts.values()].map((entry) => entry.selection));
|
|
105418
104858
|
if (!decision) {
|
|
105419
104859
|
throw AssessmentRuntimeError.from(assessmentNoEligibleTests(purpose));
|
|
105420
104860
|
}
|
|
105421
104861
|
return decision;
|
|
105422
104862
|
}
|
|
105423
|
-
|
|
105424
|
-
|
|
105425
|
-
|
|
105426
|
-
"app.assessment.attempt_id": decision.attempt.attemptId,
|
|
105427
|
-
"app.assessment.additional_attempt_ids": decision.additionalResumableAttemptIds.join(",")
|
|
105428
|
-
});
|
|
105429
|
-
}
|
|
105430
|
-
const resumed = attempts.get(decision.attempt.attemptId);
|
|
105431
|
-
return this.snapshotForResult(resumed.result, resumed.metadata, integration);
|
|
104863
|
+
pendingAwardAttempt(listing, enrollmentId) {
|
|
104864
|
+
const pending = [...listing.attempts.values()].filter((attempt) => attempt.metadata.enrollmentId === enrollmentId && this.isAwaitingAward(attempt.result));
|
|
104865
|
+
return pending.toSorted((left, right) => compareNewestCompletedAssessmentAttempt(left.selection, right.selection))[0] ?? null;
|
|
105432
104866
|
}
|
|
105433
|
-
async
|
|
105434
|
-
|
|
105435
|
-
|
|
105436
|
-
|
|
105437
|
-
"app.assessment.additional_attempt_ids": decision.additionalResumableAttemptIds.join(",")
|
|
105438
|
-
});
|
|
105439
|
-
}
|
|
105440
|
-
const resumed = attempts.get(decision.attempt.attemptId);
|
|
105441
|
-
if (this.isSettled(resumed.result)) {
|
|
105442
|
-
return this.snapshotForResult(resumed.result, resumed.metadata, integration);
|
|
105443
|
-
}
|
|
105444
|
-
if (!this.isPlatformRoutedDiagnosticMetadata(resumed.metadata)) {
|
|
105445
|
-
throw new Error("A keyed diagnostic request selected a legacy fixed attempt");
|
|
104867
|
+
async pendingAwardSnapshot(listing, context2) {
|
|
104868
|
+
const pending = this.pendingAwardAttempt(listing, context2.enrollment.id);
|
|
104869
|
+
if (!pending) {
|
|
104870
|
+
return null;
|
|
105446
104871
|
}
|
|
105447
|
-
|
|
105448
|
-
|
|
105449
|
-
await this.restoreDiagnosticChildResults({
|
|
105450
|
-
result: resumed.result,
|
|
105451
|
-
metadata: resumed.metadata,
|
|
105452
|
-
integration,
|
|
105453
|
-
assessment
|
|
104872
|
+
addEvent("assessment.pending_award_returned", {
|
|
104873
|
+
"app.assessment.attempt_id": pending.result.sourcedId
|
|
105454
104874
|
});
|
|
105455
|
-
return this.
|
|
105456
|
-
}
|
|
105457
|
-
async resumeAvailableAttempt(tests, attempts, requestFingerprint, pendingSupersessions, resume) {
|
|
105458
|
-
const select3 = () => selectRuntimeAssessment(tests, [...this.attemptsMatchingRequest(attempts, requestFingerprint).values()].map((entry) => entry.selection));
|
|
105459
|
-
return resolveAvailableAssessmentAttempt(select3(), select3, (decision) => this.resumeSnapshotOrStageSupersession(decision, attempts, pendingSupersessions, () => resume(decision)));
|
|
104875
|
+
return this.snapshotForResult(pending.result, pending.metadata, context2.integration);
|
|
105460
104876
|
}
|
|
105461
|
-
|
|
105462
|
-
|
|
105463
|
-
|
|
105464
|
-
|
|
105465
|
-
|
|
105466
|
-
throw error88;
|
|
104877
|
+
replacedAttemptIds(attempts, enrollmentId) {
|
|
104878
|
+
const replaced = new Set;
|
|
104879
|
+
for (const attempt of attempts.values()) {
|
|
104880
|
+
if (attempt.metadata.enrollmentId === enrollmentId && this.isOpen(attempt.result)) {
|
|
104881
|
+
replaced.add(attempt.result.sourcedId);
|
|
105467
104882
|
}
|
|
105468
|
-
if (shouldConfirmAssessmentVersionUnavailable(error88.details)) {
|
|
105469
|
-
await sleep(TimebackAssessmentRuntimeService.ASSESSMENT_VERSION_CONFIRMATION_DELAY_MS);
|
|
105470
|
-
try {
|
|
105471
|
-
return await resume();
|
|
105472
|
-
} catch (confirmationError) {
|
|
105473
|
-
if (!(confirmationError instanceof AssessmentRuntimeError) || confirmationError.runtimeCode !== "SELECTED_TEST_VERSION_UNAVAILABLE") {
|
|
105474
|
-
throw confirmationError;
|
|
105475
|
-
}
|
|
105476
|
-
}
|
|
105477
|
-
}
|
|
105478
|
-
const staged = attempts.get(decision.attempt.attemptId);
|
|
105479
|
-
pendingSupersessions.add(staged.result.sourcedId);
|
|
105480
|
-
stageAssessmentAttemptSupersession(staged);
|
|
105481
|
-
addEvent("assessment.attempt_supersession_staged", {
|
|
105482
|
-
"app.assessment.attempt_id": decision.attempt.attemptId,
|
|
105483
|
-
"app.assessment.reason": "selected_test_version_unavailable"
|
|
105484
|
-
});
|
|
105485
|
-
return null;
|
|
105486
104883
|
}
|
|
104884
|
+
return replaced;
|
|
104885
|
+
}
|
|
104886
|
+
supersedeReplacedAttempts(db2, attempts, enrollmentId) {
|
|
104887
|
+
return this.supersedeAttemptsBestEffort(db2, this.replacedAttemptIds(attempts, enrollmentId));
|
|
105487
104888
|
}
|
|
105488
|
-
async
|
|
104889
|
+
async supersedeAttempts(db2, attemptIds) {
|
|
105489
104890
|
for (const attemptId of [...attemptIds].toSorted()) {
|
|
105490
104891
|
await this.deps.assessmentRuntimeLock(db2, [assessmentRuntimeAttemptLockKey(attemptId)], async () => {
|
|
105491
|
-
|
|
105492
|
-
|
|
105493
|
-
result = await this.requireClient().api.oneroster.assessmentResults.get(attemptId);
|
|
105494
|
-
} catch (error88) {
|
|
105495
|
-
if (isApiError(error88) && error88.statusCode === 404) {
|
|
105496
|
-
return;
|
|
105497
|
-
}
|
|
105498
|
-
throw error88;
|
|
105499
|
-
}
|
|
105500
|
-
const update2 = buildAssessmentAttemptSupersessionUpdate(result);
|
|
104892
|
+
const result = await this.findAttemptResult(attemptId);
|
|
104893
|
+
const update2 = result && buildAssessmentAttemptSupersessionUpdate(result);
|
|
105501
104894
|
if (!update2) {
|
|
105502
104895
|
return;
|
|
105503
104896
|
}
|
|
105504
104897
|
await this.requireClient().api.oneroster.assessmentResults.upsert(attemptId, update2);
|
|
105505
104898
|
addEvent("assessment.attempt_superseded", {
|
|
105506
104899
|
"app.assessment.attempt_id": attemptId,
|
|
105507
|
-
"app.assessment.reason": "
|
|
104900
|
+
"app.assessment.reason": "replaced_by_fresh_start"
|
|
105508
104901
|
});
|
|
105509
104902
|
});
|
|
105510
104903
|
}
|
|
105511
104904
|
}
|
|
105512
|
-
async
|
|
104905
|
+
async supersedeAttemptsBestEffort(db2, attemptIds) {
|
|
105513
104906
|
if (attemptIds.size === 0) {
|
|
105514
104907
|
return;
|
|
105515
104908
|
}
|
|
105516
104909
|
try {
|
|
105517
|
-
await this.
|
|
104910
|
+
await this.supersedeAttempts(db2, attemptIds);
|
|
105518
104911
|
} catch (error88) {
|
|
105519
104912
|
addEvent("assessment.attempt_supersession_persist_failed", {
|
|
105520
104913
|
"app.assessment.attempt_ids": [...attemptIds].toSorted().join(","),
|
|
@@ -105523,43 +104916,6 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
105523
104916
|
});
|
|
105524
104917
|
}
|
|
105525
104918
|
}
|
|
105526
|
-
async resumeReviewSnapshot(decision, attempts, existingChildResults, context2) {
|
|
105527
|
-
if (decision.additionalResumableAttemptIds.length > 0) {
|
|
105528
|
-
addEvent("assessment.multiple_resumable_attempts", {
|
|
105529
|
-
"app.assessment.attempt_id": decision.attempt.attemptId,
|
|
105530
|
-
"app.assessment.additional_attempt_ids": decision.additionalResumableAttemptIds.join(",")
|
|
105531
|
-
});
|
|
105532
|
-
}
|
|
105533
|
-
const resumed = attempts.get(decision.attempt.attemptId);
|
|
105534
|
-
if (resumed.metadata.purpose !== "review") {
|
|
105535
|
-
throw new Error("A standards-review request selected a non-review attempt");
|
|
105536
|
-
}
|
|
105537
|
-
const reviewMetadata = resumed.metadata;
|
|
105538
|
-
let assessment;
|
|
105539
|
-
const current = await reconcileReviewChildResults({
|
|
105540
|
-
initial: resumed,
|
|
105541
|
-
writeOpen: async () => {
|
|
105542
|
-
const source = await this.loadPinnedReviewBankSource(reviewMetadata);
|
|
105543
|
-
assessment = projectReviewAssessment(source.assessment, source.bank, reviewMetadata.review.selections);
|
|
105544
|
-
await this.ensureReviewChildResults({
|
|
105545
|
-
result: resumed.result,
|
|
105546
|
-
metadata: reviewMetadata,
|
|
105547
|
-
integration: context2.integration,
|
|
105548
|
-
source,
|
|
105549
|
-
assessment,
|
|
105550
|
-
existingChildResults,
|
|
105551
|
-
lookupMissingResults: true
|
|
105552
|
-
});
|
|
105553
|
-
},
|
|
105554
|
-
crossBarrier: () => crossAssessmentAttemptLockBarrier(this.deps.assessmentRuntimeLock, this.deps.db, resumed.result.sourcedId),
|
|
105555
|
-
readLatest: () => this.peekAttempt({
|
|
105556
|
-
studentId: resumed.result.student.sourcedId,
|
|
105557
|
-
attemptId: resumed.result.sourcedId
|
|
105558
|
-
}),
|
|
105559
|
-
restoreTerminal: (latest) => this.restoreAllCompletedReviewChildResponses(latest.result, latest.metadata)
|
|
105560
|
-
});
|
|
105561
|
-
return assessmentChildProjectionIsTerminal(current.result) ? this.snapshotForResult(current.result, current.metadata, context2.integration) : this.snapshot(current.result, current.metadata, assessment, context2.integration);
|
|
105562
|
-
}
|
|
105563
104919
|
recordReviewPreparationPhase(phase, startedAt, counts = {}) {
|
|
105564
104920
|
addEvent("assessment.review_preparation_phase", {
|
|
105565
104921
|
"app.assessment.review_phase": phase,
|
|
@@ -105910,12 +105266,7 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
105910
105266
|
}
|
|
105911
105267
|
});
|
|
105912
105268
|
} catch (error88) {
|
|
105913
|
-
|
|
105914
|
-
throw error88;
|
|
105915
|
-
}
|
|
105916
|
-
addEvent("assessment.line_item_create_conflict", {
|
|
105917
|
-
"app.assessment.line_item_id": lineItemId
|
|
105918
|
-
});
|
|
105269
|
+
await this.confirmLineItemAfterRefusedCreate(lineItemId, error88, "assessment.line_item_create_conflict", () => client2.api.oneroster.assessmentLineItems.get(lineItemId));
|
|
105919
105270
|
}
|
|
105920
105271
|
return lineItemId;
|
|
105921
105272
|
}
|
|
@@ -105957,144 +105308,99 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
105957
105308
|
}
|
|
105958
105309
|
});
|
|
105959
105310
|
} catch (error88) {
|
|
105960
|
-
|
|
105961
|
-
throw error88;
|
|
105962
|
-
}
|
|
105963
|
-
addEvent("assessment.review_bank_line_item_create_conflict", {
|
|
105964
|
-
"app.assessment.line_item_id": lineItemId
|
|
105965
|
-
});
|
|
105311
|
+
await this.confirmLineItemAfterRefusedCreate(lineItemId, error88, "assessment.review_bank_line_item_create_conflict", () => client2.api.oneroster.assessmentLineItems.get(lineItemId));
|
|
105966
105312
|
}
|
|
105967
105313
|
return lineItemId;
|
|
105968
105314
|
}
|
|
105969
|
-
|
|
105970
|
-
|
|
105971
|
-
|
|
105972
|
-
|
|
105973
|
-
|
|
105974
|
-
|
|
105975
|
-
|
|
105976
|
-
|
|
105315
|
+
async confirmLineItemAfterRefusedCreate(lineItemId, createError, event, read) {
|
|
105316
|
+
let stored;
|
|
105317
|
+
try {
|
|
105318
|
+
stored = await read();
|
|
105319
|
+
} catch (readError) {
|
|
105320
|
+
if (isApiError(readError) && readError.statusCode === 404) {
|
|
105321
|
+
throw createError;
|
|
105322
|
+
}
|
|
105323
|
+
throw readError;
|
|
105977
105324
|
}
|
|
105325
|
+
addEvent(event, {
|
|
105326
|
+
"app.assessment.line_item_id": lineItemId,
|
|
105327
|
+
...isApiError(createError) ? { "app.timeback.status": createError.statusCode } : {}
|
|
105328
|
+
});
|
|
105329
|
+
return stored;
|
|
105330
|
+
}
|
|
105331
|
+
tokenContext(attempt) {
|
|
105978
105332
|
return {
|
|
105979
|
-
|
|
105333
|
+
version: 1,
|
|
105980
105334
|
gameId: attempt.integration.gameId,
|
|
105981
105335
|
studentId: attempt.result.student.sourcedId,
|
|
105982
|
-
|
|
105983
|
-
|
|
105984
|
-
|
|
105985
|
-
enrollmentId: metadata2.enrollmentId,
|
|
105986
|
-
selectedTest: {
|
|
105987
|
-
assessmentKey,
|
|
105988
|
-
identifier: metadata2.selectedTest.identifier,
|
|
105989
|
-
contentRevision: metadata2.selectedTest.contentRevision
|
|
105990
|
-
}
|
|
105991
|
-
};
|
|
105992
|
-
}
|
|
105993
|
-
reviewChildResultContext(metadata2, submission) {
|
|
105994
|
-
const selection = metadata2.review.selections.find((candidate) => candidate.itemIdentifier === submission.itemIdentifier);
|
|
105995
|
-
if (!selection) {
|
|
105996
|
-
throw new Error(`Committed review item ${submission.itemIdentifier} is not in its pinned selection`);
|
|
105997
|
-
}
|
|
105998
|
-
return {
|
|
105999
|
-
kind: "review",
|
|
106000
|
-
bankRevision: metadata2.review.bankRevision,
|
|
106001
|
-
standard: selection.standard
|
|
105336
|
+
attemptId: attempt.result.sourcedId,
|
|
105337
|
+
parentLineItemId: attempt.result.assessmentLineItem.sourcedId,
|
|
105338
|
+
metadata: assessmentTokenMetadata(attempt.metadata)
|
|
106002
105339
|
};
|
|
106003
105340
|
}
|
|
106004
|
-
|
|
106005
|
-
|
|
106006
|
-
|
|
106007
|
-
|
|
106008
|
-
|
|
106009
|
-
|
|
105341
|
+
async ensureTranscriptLineItems(context2, assessment) {
|
|
105342
|
+
await runWithConcurrency(assessment.items, TimebackAssessmentRuntimeService.SCORING_CONCURRENCY, async (item) => {
|
|
105343
|
+
const id = await assessmentTranscriptChildLineItemId(context2, item.identifier);
|
|
105344
|
+
if (this.childLineItemCache.get(id)) {
|
|
105345
|
+
return;
|
|
105346
|
+
}
|
|
105347
|
+
const metadata2 = context2.metadata;
|
|
105348
|
+
const common2 = {
|
|
105349
|
+
parentLineItemId: context2.parentLineItemId,
|
|
105350
|
+
integration: { courseId: metadata2.courseId },
|
|
105351
|
+
item
|
|
105352
|
+
};
|
|
105353
|
+
if (metadata2.purpose === "review") {
|
|
105354
|
+
const standard = metadata2.review.selections.find((selection) => selection.itemIdentifier === item.identifier)?.standard;
|
|
105355
|
+
if (!standard) {
|
|
105356
|
+
throw new Error("Review candidate is not in the pinned selection");
|
|
105357
|
+
}
|
|
105358
|
+
await this.ensureReviewQuestionLineItem({
|
|
105359
|
+
...common2,
|
|
105360
|
+
bankRevision: metadata2.review.bankRevision,
|
|
105361
|
+
standards: [standard]
|
|
105362
|
+
});
|
|
105363
|
+
} else if (this.isPlatformRoutedDiagnosticMetadata(metadata2)) {
|
|
105364
|
+
await this.ensureDiagnosticQuestionLineItem({ ...common2, metadata: metadata2 });
|
|
105365
|
+
} else {
|
|
105366
|
+
const expected = {
|
|
105367
|
+
status: "active",
|
|
105368
|
+
parentAssessmentLineItem: { sourcedId: context2.parentLineItemId },
|
|
105369
|
+
course: { sourcedId: metadata2.courseId },
|
|
105370
|
+
resultValueMin: 0,
|
|
105371
|
+
resultValueMax: item.maxScore,
|
|
105372
|
+
metadata: {
|
|
105373
|
+
playcademyAssessmentQuestionDefinition: {
|
|
105374
|
+
version: 1,
|
|
105375
|
+
itemIdentifier: item.identifier,
|
|
105376
|
+
contentRevision: metadata2.selectedTest.contentRevision
|
|
105377
|
+
}
|
|
105378
|
+
}
|
|
105379
|
+
};
|
|
105380
|
+
const store = this.requireClient().api.oneroster.assessmentLineItems;
|
|
105381
|
+
try {
|
|
105382
|
+
await store.create({ sourcedId: id, title: item.title, ...expected });
|
|
105383
|
+
} catch (error88) {
|
|
105384
|
+
this.assertAssessmentChildLineItem(id, await this.confirmLineItemAfterRefusedCreate(id, error88, "assessment.question_line_item_create_conflict", () => store.get(id)), expected);
|
|
105385
|
+
}
|
|
105386
|
+
}
|
|
105387
|
+
this.childLineItemCache.set(id, true);
|
|
106010
105388
|
});
|
|
106011
105389
|
}
|
|
106012
|
-
|
|
106013
|
-
return
|
|
106014
|
-
|
|
106015
|
-
|
|
106016
|
-
|
|
106017
|
-
|
|
106018
|
-
|
|
106019
|
-
|
|
106020
|
-
|
|
106021
|
-
|
|
106022
|
-
|
|
105390
|
+
async reconcileTranscriptChildren(context2, graded, only) {
|
|
105391
|
+
return reconcileAssessmentTranscriptChildren({
|
|
105392
|
+
context: context2,
|
|
105393
|
+
graded,
|
|
105394
|
+
only,
|
|
105395
|
+
assessmentResults: this.requireClient().api.oneroster.assessmentResults,
|
|
105396
|
+
ensureLineItems: (assessment) => this.ensureTranscriptLineItems(context2, assessment),
|
|
105397
|
+
onPersisted: (submissionId) => addEvent("assessment.child_result_persisted", {
|
|
105398
|
+
"app.assessment.attempt_id": context2.attemptId,
|
|
105399
|
+
"app.assessment.submission_id": submissionId,
|
|
105400
|
+
"app.assessment.child_result_outcome": "upserted"
|
|
106023
105401
|
})
|
|
106024
105402
|
});
|
|
106025
105403
|
}
|
|
106026
|
-
async prepareAssessmentChildResult(input) {
|
|
106027
|
-
try {
|
|
106028
|
-
const attemptContext = this.assessmentChildAttempt({
|
|
106029
|
-
...input.attempt,
|
|
106030
|
-
metadata: input.metadata
|
|
106031
|
-
});
|
|
106032
|
-
if (!attemptContext) {
|
|
106033
|
-
return null;
|
|
106034
|
-
}
|
|
106035
|
-
const parentLineItemId = input.attempt.result.assessmentLineItem.sourcedId;
|
|
106036
|
-
const { itemIdentifier } = input.submission;
|
|
106037
|
-
const context2 = input.context();
|
|
106038
|
-
const [command, childLineItemId] = await Promise.all([
|
|
106039
|
-
buildSubmissionChildResultCommand({
|
|
106040
|
-
attempt: attemptContext,
|
|
106041
|
-
submission: input.submission,
|
|
106042
|
-
responses: input.metadata.responses[itemIdentifier],
|
|
106043
|
-
grading: input.submission.grading ?? QTI_CHILD_GRADING,
|
|
106044
|
-
context: context2
|
|
106045
|
-
}),
|
|
106046
|
-
context2.kind === "review" ? reviewQuestionLineItemId(parentLineItemId, itemIdentifier) : diagnosticQuestionLineItemId(parentLineItemId, itemIdentifier)
|
|
106047
|
-
]);
|
|
106048
|
-
return { command, childLineItemId };
|
|
106049
|
-
} catch (error88) {
|
|
106050
|
-
addEvent("assessment.child_result_prepare_failed", {
|
|
106051
|
-
"app.assessment.attempt_id": input.attempt.result.sourcedId,
|
|
106052
|
-
"app.assessment.submission_id": input.submission.submissionId,
|
|
106053
|
-
"app.assessment.qti_item_identifier": input.submission.itemIdentifier,
|
|
106054
|
-
"exception.type": errorType(error88),
|
|
106055
|
-
"app.error.message": errorMessage(error88)
|
|
106056
|
-
});
|
|
106057
|
-
return null;
|
|
106058
|
-
}
|
|
106059
|
-
}
|
|
106060
|
-
async projectPreparedAssessmentChildResult(prepared) {
|
|
106061
|
-
if (!prepared) {
|
|
106062
|
-
return;
|
|
106063
|
-
}
|
|
106064
|
-
const startedAt = Date.now();
|
|
106065
|
-
const identity = {
|
|
106066
|
-
"app.assessment.attempt_id": prepared.command.payload.attempt.attemptId,
|
|
106067
|
-
"app.assessment.submission_id": prepared.command.payload.administration.submissionId
|
|
106068
|
-
};
|
|
106069
|
-
try {
|
|
106070
|
-
const disposition = await persistAssessmentChildResult({
|
|
106071
|
-
assessmentResults: this.requireClient().api.oneroster.assessmentResults,
|
|
106072
|
-
command: prepared.command,
|
|
106073
|
-
childLineItemId: prepared.childLineItemId
|
|
106074
|
-
});
|
|
106075
|
-
if (disposition.outcome === "conflict") {
|
|
106076
|
-
addEvent("assessment.child_result_conflict", {
|
|
106077
|
-
...identity,
|
|
106078
|
-
"app.assessment.child_result_id": disposition.resultId,
|
|
106079
|
-
"app.assessment.child_result_conflict_reason": disposition.reason
|
|
106080
|
-
});
|
|
106081
|
-
return;
|
|
106082
|
-
}
|
|
106083
|
-
addEvent("assessment.child_result_persisted", {
|
|
106084
|
-
...identity,
|
|
106085
|
-
"app.assessment.child_result_id": disposition.resultId,
|
|
106086
|
-
"app.assessment.child_result_outcome": disposition.outcome,
|
|
106087
|
-
"app.assessment.duration_ms": Date.now() - startedAt
|
|
106088
|
-
});
|
|
106089
|
-
} catch (error88) {
|
|
106090
|
-
addEvent("assessment.child_result_persist_failed", {
|
|
106091
|
-
...identity,
|
|
106092
|
-
"app.assessment.duration_ms": Date.now() - startedAt,
|
|
106093
|
-
"exception.type": errorType(error88),
|
|
106094
|
-
"app.error.message": errorMessage(error88)
|
|
106095
|
-
});
|
|
106096
|
-
}
|
|
106097
|
-
}
|
|
106098
105404
|
assertAssessmentChildLineItem(lineItemId, stored, expected) {
|
|
106099
105405
|
if (assessmentChildLineItemMatches(stored, expected)) {
|
|
106100
105406
|
return;
|
|
@@ -106141,102 +105447,11 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
106141
105447
|
...expected
|
|
106142
105448
|
});
|
|
106143
105449
|
} catch (error88) {
|
|
106144
|
-
|
|
106145
|
-
throw error88;
|
|
106146
|
-
}
|
|
106147
|
-
const stored = await client2.api.oneroster.assessmentLineItems.get(lineItemId);
|
|
105450
|
+
const stored = await this.confirmLineItemAfterRefusedCreate(lineItemId, error88, "assessment.diagnostic_question_line_item_create_conflict", () => client2.api.oneroster.assessmentLineItems.get(lineItemId));
|
|
106148
105451
|
this.assertAssessmentChildLineItem(lineItemId, stored, expected);
|
|
106149
|
-
addEvent("assessment.diagnostic_question_line_item_create_conflict", {
|
|
106150
|
-
"app.assessment.line_item_id": lineItemId
|
|
106151
|
-
});
|
|
106152
105452
|
}
|
|
106153
105453
|
return lineItemId;
|
|
106154
105454
|
}
|
|
106155
|
-
async projectDiagnosticItemResponse(projection, assessment, checkpoint = null) {
|
|
106156
|
-
try {
|
|
106157
|
-
const loadedAssessment = assessment ?? await this.loadAttemptAssessment(projection.metadata);
|
|
106158
|
-
const item = loadedAssessment.items.find((candidate) => candidate.identifier === projection.ledgerEntry.itemIdentifier);
|
|
106159
|
-
if (!item) {
|
|
106160
|
-
throw new Error(`Diagnostic assessment is missing item ${projection.ledgerEntry.itemIdentifier}`);
|
|
106161
|
-
}
|
|
106162
|
-
const childLineItemId = await this.ensureDiagnosticQuestionLineItem({
|
|
106163
|
-
parentLineItemId: projection.result.assessmentLineItem.sourcedId,
|
|
106164
|
-
integration: projection.integration,
|
|
106165
|
-
metadata: projection.metadata,
|
|
106166
|
-
item
|
|
106167
|
-
});
|
|
106168
|
-
if (checkpoint && checkpoint.childLineItemId !== childLineItemId) {
|
|
106169
|
-
throw new Error("Prepared diagnostic checkpoint resolved a different line item");
|
|
106170
|
-
}
|
|
106171
|
-
const childResultId = await diagnosticQuestionResultId(projection.result.sourcedId, childLineItemId);
|
|
106172
|
-
const finalized = buildFinalizedDiagnosticChildResult({
|
|
106173
|
-
childLineItemId,
|
|
106174
|
-
student: projection.result.student,
|
|
106175
|
-
parentAttemptId: projection.result.sourcedId,
|
|
106176
|
-
metadata: projection.metadata,
|
|
106177
|
-
ledgerEntry: projection.ledgerEntry
|
|
106178
|
-
});
|
|
106179
|
-
await Promise.all([
|
|
106180
|
-
this.projectPreparedAssessmentChildResult(checkpoint),
|
|
106181
|
-
this.requireClient().api.oneroster.assessmentResults.upsert(childResultId, finalized.resultUpdate)
|
|
106182
|
-
]);
|
|
106183
|
-
} catch (error88) {
|
|
106184
|
-
addEvent("assessment.diagnostic_child_projection_failed", {
|
|
106185
|
-
"app.assessment.attempt_id": projection.result.sourcedId,
|
|
106186
|
-
"app.assessment.qti_item_identifier": projection.ledgerEntry.itemIdentifier,
|
|
106187
|
-
"exception.type": errorType(error88),
|
|
106188
|
-
"app.error.message": errorMessage(error88)
|
|
106189
|
-
});
|
|
106190
|
-
}
|
|
106191
|
-
}
|
|
106192
|
-
async restoreDiagnosticChildResults(attempt) {
|
|
106193
|
-
const assessment = attempt.assessment ?? await this.loadAttemptAssessment(attempt.metadata);
|
|
106194
|
-
await runWithConcurrency(attempt.metadata.diagnostic.ledger, TimebackAssessmentRuntimeService.SCORING_CONCURRENCY, async (ledgerEntry) => {
|
|
106195
|
-
try {
|
|
106196
|
-
const childLineItemId = await diagnosticQuestionLineItemId(attempt.result.assessmentLineItem.sourcedId, ledgerEntry.itemIdentifier);
|
|
106197
|
-
const childResultId = await diagnosticQuestionResultId(attempt.result.sourcedId, childLineItemId);
|
|
106198
|
-
let childResult;
|
|
106199
|
-
try {
|
|
106200
|
-
childResult = await this.requireClient().api.oneroster.assessmentResults.get(childResultId);
|
|
106201
|
-
} catch (error88) {
|
|
106202
|
-
if (!isApiError(error88) || error88.statusCode !== 404) {
|
|
106203
|
-
throw error88;
|
|
106204
|
-
}
|
|
106205
|
-
}
|
|
106206
|
-
const resolution = resolveDiagnosticChildResult({
|
|
106207
|
-
childResult,
|
|
106208
|
-
childLineItemId,
|
|
106209
|
-
parentResult: attempt.result,
|
|
106210
|
-
parentMetadata: attempt.metadata,
|
|
106211
|
-
ledgerEntry
|
|
106212
|
-
});
|
|
106213
|
-
if (resolution.action === "reuse") {
|
|
106214
|
-
return;
|
|
106215
|
-
}
|
|
106216
|
-
if (resolution.action === "restore") {
|
|
106217
|
-
addEvent("assessment.diagnostic_child_result_restore", {
|
|
106218
|
-
"app.assessment.diagnostic_child_restore_reason": resolution.reason,
|
|
106219
|
-
"app.assessment.attempt_id": attempt.result.sourcedId,
|
|
106220
|
-
"app.assessment.child_result_id": childResultId,
|
|
106221
|
-
"app.assessment.qti_item_identifier": ledgerEntry.itemIdentifier
|
|
106222
|
-
});
|
|
106223
|
-
}
|
|
106224
|
-
await this.projectDiagnosticItemResponse({
|
|
106225
|
-
result: attempt.result,
|
|
106226
|
-
metadata: attempt.metadata,
|
|
106227
|
-
ledgerEntry,
|
|
106228
|
-
integration: attempt.integration
|
|
106229
|
-
}, assessment);
|
|
106230
|
-
} catch (error88) {
|
|
106231
|
-
addEvent("assessment.diagnostic_child_reconciliation_failed", {
|
|
106232
|
-
"app.assessment.attempt_id": attempt.result.sourcedId,
|
|
106233
|
-
"app.assessment.qti_item_identifier": ledgerEntry.itemIdentifier,
|
|
106234
|
-
"exception.type": errorType(error88),
|
|
106235
|
-
"app.error.message": errorMessage(error88)
|
|
106236
|
-
});
|
|
106237
|
-
}
|
|
106238
|
-
});
|
|
106239
|
-
}
|
|
106240
105455
|
async ensureReviewQuestionLineItem(input) {
|
|
106241
105456
|
const client2 = this.requireClient();
|
|
106242
105457
|
const lineItemId = await reviewQuestionLineItemId(input.parentLineItemId, input.item.identifier);
|
|
@@ -106272,231 +105487,11 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
106272
105487
|
...expected
|
|
106273
105488
|
});
|
|
106274
105489
|
} catch (error88) {
|
|
106275
|
-
|
|
106276
|
-
throw error88;
|
|
106277
|
-
}
|
|
106278
|
-
const stored = await client2.api.oneroster.assessmentLineItems.get(lineItemId);
|
|
105490
|
+
const stored = await this.confirmLineItemAfterRefusedCreate(lineItemId, error88, "assessment.review_question_line_item_create_conflict", () => client2.api.oneroster.assessmentLineItems.get(lineItemId));
|
|
106279
105491
|
this.assertAssessmentChildLineItem(lineItemId, stored, expected);
|
|
106280
|
-
addEvent("assessment.review_question_line_item_create_conflict", {
|
|
106281
|
-
"app.assessment.line_item_id": lineItemId
|
|
106282
|
-
});
|
|
106283
105492
|
}
|
|
106284
105493
|
return lineItemId;
|
|
106285
105494
|
}
|
|
106286
|
-
async putOpenReviewChildResult(input) {
|
|
106287
|
-
const metadata2 = buildReviewItemResultMetadata({
|
|
106288
|
-
metadata: input.parentMetadata,
|
|
106289
|
-
selection: input.selection,
|
|
106290
|
-
parentAttemptId: input.parentResult.sourcedId
|
|
106291
|
-
});
|
|
106292
|
-
await this.requireClient().api.oneroster.assessmentResults.upsert(input.childResultId, buildOpenReviewChildResult({
|
|
106293
|
-
childLineItemId: input.childLineItemId,
|
|
106294
|
-
student: input.parentResult.student,
|
|
106295
|
-
metadata: metadata2
|
|
106296
|
-
}));
|
|
106297
|
-
}
|
|
106298
|
-
async ensureReviewChildResults(input) {
|
|
106299
|
-
const itemByIdentifier = new Map(input.assessment.items.map((item) => [item.identifier, item]));
|
|
106300
|
-
const bankItemByIdentifier = new Map(input.source.bank.items.map((item) => [item.itemIdentifier, item]));
|
|
106301
|
-
const physicalItems = new Set(input.metadata.review.selections.map((selection) => selection.itemIdentifier));
|
|
106302
|
-
if (physicalItems.size !== input.metadata.review.selections.length) {
|
|
106303
|
-
throw new Error("MVP review selection must use each physical item at most once");
|
|
106304
|
-
}
|
|
106305
|
-
const administeredItems = administeredItemIdentifiers(input.metadata.itemSubmissions);
|
|
106306
|
-
await runWithConcurrency(input.metadata.review.selections, TimebackAssessmentRuntimeService.SCORING_CONCURRENCY, async (selection) => {
|
|
106307
|
-
const item = itemByIdentifier.get(selection.itemIdentifier);
|
|
106308
|
-
const indexed = bankItemByIdentifier.get(selection.itemIdentifier);
|
|
106309
|
-
if (!item || !indexed) {
|
|
106310
|
-
throw new Error(`Review assessment or bank is missing selected item ${selection.itemIdentifier}`);
|
|
106311
|
-
}
|
|
106312
|
-
const childLineItemId = await this.ensureReviewQuestionLineItem({
|
|
106313
|
-
parentLineItemId: input.result.assessmentLineItem.sourcedId,
|
|
106314
|
-
integration: input.integration,
|
|
106315
|
-
bankRevision: input.metadata.review.bankRevision,
|
|
106316
|
-
standards: [...indexed.standards],
|
|
106317
|
-
item
|
|
106318
|
-
});
|
|
106319
|
-
if (!administeredItems.has(selection.itemIdentifier)) {
|
|
106320
|
-
return;
|
|
106321
|
-
}
|
|
106322
|
-
const childResultId = await reviewQuestionResultId(input.result.sourcedId, childLineItemId);
|
|
106323
|
-
const streamedChildResult = input.existingChildResults.get(childResultId);
|
|
106324
|
-
let childResult = streamedChildResult;
|
|
106325
|
-
if (!childResult && input.lookupMissingResults) {
|
|
106326
|
-
try {
|
|
106327
|
-
childResult = await this.requireClient().api.oneroster.assessmentResults.get(childResultId);
|
|
106328
|
-
} catch (error88) {
|
|
106329
|
-
if (!isApiError(error88) || error88.statusCode !== 404) {
|
|
106330
|
-
throw error88;
|
|
106331
|
-
}
|
|
106332
|
-
}
|
|
106333
|
-
}
|
|
106334
|
-
const resolution = resolveReviewChildResult({
|
|
106335
|
-
childResult,
|
|
106336
|
-
childLineItemId,
|
|
106337
|
-
parentResult: input.result,
|
|
106338
|
-
parentMetadata: input.metadata,
|
|
106339
|
-
selection
|
|
106340
|
-
});
|
|
106341
|
-
if (resolution.action === "reuse") {
|
|
106342
|
-
return;
|
|
106343
|
-
}
|
|
106344
|
-
if (resolution.action === "restore") {
|
|
106345
|
-
addEvent("assessment.review_child_result_restore", {
|
|
106346
|
-
"app.assessment.review_child_restore_reason": resolution.reason,
|
|
106347
|
-
"app.assessment.attempt_id": input.result.sourcedId,
|
|
106348
|
-
"app.assessment.child_result_id": childResultId,
|
|
106349
|
-
"app.assessment.qti_item_identifier": selection.itemIdentifier
|
|
106350
|
-
});
|
|
106351
|
-
}
|
|
106352
|
-
await this.putOpenReviewChildResult({
|
|
106353
|
-
childResultId,
|
|
106354
|
-
childLineItemId,
|
|
106355
|
-
parentResult: input.result,
|
|
106356
|
-
parentMetadata: input.metadata,
|
|
106357
|
-
selection
|
|
106358
|
-
});
|
|
106359
|
-
});
|
|
106360
|
-
}
|
|
106361
|
-
async putReviewChildResponse(projection, checkpoint) {
|
|
106362
|
-
const { result, metadata: metadata2, itemIdentifier, integration } = projection;
|
|
106363
|
-
const selection = metadata2.review.selections.find((candidate) => candidate.itemIdentifier === itemIdentifier);
|
|
106364
|
-
if (!selection) {
|
|
106365
|
-
return;
|
|
106366
|
-
}
|
|
106367
|
-
try {
|
|
106368
|
-
const source = await this.loadPinnedReviewBankSource(metadata2);
|
|
106369
|
-
const item = source.assessment.items.find((candidate) => candidate.identifier === selection.itemIdentifier);
|
|
106370
|
-
const indexed = source.bank.items.find((candidate) => candidate.itemIdentifier === selection.itemIdentifier);
|
|
106371
|
-
if (!item || !indexed) {
|
|
106372
|
-
throw new Error(`Review assessment or bank is missing selected item ${selection.itemIdentifier}`);
|
|
106373
|
-
}
|
|
106374
|
-
const childLineItemId = await this.ensureReviewQuestionLineItem({
|
|
106375
|
-
parentLineItemId: result.assessmentLineItem.sourcedId,
|
|
106376
|
-
integration,
|
|
106377
|
-
bankRevision: metadata2.review.bankRevision,
|
|
106378
|
-
standards: [...indexed.standards],
|
|
106379
|
-
item
|
|
106380
|
-
});
|
|
106381
|
-
if (checkpoint && checkpoint.childLineItemId !== childLineItemId) {
|
|
106382
|
-
throw new Error("Prepared review checkpoint resolved a different line item");
|
|
106383
|
-
}
|
|
106384
|
-
const childResultId = await reviewQuestionResultId(result.sourcedId, childLineItemId);
|
|
106385
|
-
const childMetadata = buildReviewItemResultMetadata({
|
|
106386
|
-
metadata: metadata2,
|
|
106387
|
-
selection,
|
|
106388
|
-
parentAttemptId: result.sourcedId
|
|
106389
|
-
});
|
|
106390
|
-
await Promise.all([
|
|
106391
|
-
this.projectPreparedAssessmentChildResult(checkpoint),
|
|
106392
|
-
this.requireClient().api.oneroster.assessmentResults.upsert(childResultId, buildOpenReviewChildResult({
|
|
106393
|
-
childLineItemId,
|
|
106394
|
-
student: result.student,
|
|
106395
|
-
metadata: childMetadata
|
|
106396
|
-
}))
|
|
106397
|
-
]);
|
|
106398
|
-
} catch (error88) {
|
|
106399
|
-
addEvent("assessment.review_child_response_projection_failed", {
|
|
106400
|
-
"app.assessment.attempt_id": result.sourcedId,
|
|
106401
|
-
"app.assessment.qti_item_identifier": selection.itemIdentifier,
|
|
106402
|
-
"exception.type": errorType(error88),
|
|
106403
|
-
"app.error.message": errorMessage(error88)
|
|
106404
|
-
});
|
|
106405
|
-
}
|
|
106406
|
-
}
|
|
106407
|
-
async projectReviewItemResponse(params, projection, checkpoint) {
|
|
106408
|
-
const terminal = assessmentChildProjectionIsTerminal(projection.result);
|
|
106409
|
-
try {
|
|
106410
|
-
await reconcileReviewChildResults({
|
|
106411
|
-
initial: projection,
|
|
106412
|
-
writeOpen: () => this.putReviewChildResponse(projection, checkpoint),
|
|
106413
|
-
crossBarrier: () => crossAssessmentAttemptLockBarrier(this.deps.assessmentRuntimeLock, this.deps.db, params.attemptId),
|
|
106414
|
-
readLatest: () => this.peekAttempt(params),
|
|
106415
|
-
restoreTerminal: async (latest) => {
|
|
106416
|
-
const repair = this.restoreCompletedReviewChildResponses(latest.result, latest.metadata, new Set([projection.itemIdentifier]));
|
|
106417
|
-
if (terminal) {
|
|
106418
|
-
await Promise.allSettled([
|
|
106419
|
-
this.projectPreparedAssessmentChildResult(checkpoint),
|
|
106420
|
-
repair
|
|
106421
|
-
]);
|
|
106422
|
-
} else {
|
|
106423
|
-
await repair;
|
|
106424
|
-
}
|
|
106425
|
-
}
|
|
106426
|
-
});
|
|
106427
|
-
} catch (error88) {
|
|
106428
|
-
addEvent("assessment.review_child_projection_barrier_failed", {
|
|
106429
|
-
"app.assessment.attempt_id": params.attemptId,
|
|
106430
|
-
"app.assessment.qti_item_identifier": projection.itemIdentifier,
|
|
106431
|
-
"exception.type": errorType(error88),
|
|
106432
|
-
"app.error.message": errorMessage(error88)
|
|
106433
|
-
});
|
|
106434
|
-
}
|
|
106435
|
-
}
|
|
106436
|
-
restoreAllCompletedReviewChildResponses(result, metadata2) {
|
|
106437
|
-
return this.restoreCompletedReviewChildResponses(result, metadata2, new Set(metadata2.itemSubmissions.map((item) => item.itemIdentifier)));
|
|
106438
|
-
}
|
|
106439
|
-
async restoreCompletedReviewChildResponses(result, metadata2, changedItemIdentifiers) {
|
|
106440
|
-
const outcomes = metadata2.completion?.itemOutcomes;
|
|
106441
|
-
const submissionId = metadata2.submissionId;
|
|
106442
|
-
if (!submissionId || !outcomes) {
|
|
106443
|
-
addEvent("assessment.review_child_terminal_restore_unavailable", {
|
|
106444
|
-
"app.assessment.attempt_id": result.sourcedId
|
|
106445
|
-
});
|
|
106446
|
-
return;
|
|
106447
|
-
}
|
|
106448
|
-
const outcomeByItem = new Map(outcomes.map((outcome) => [outcome.itemIdentifier, outcome]));
|
|
106449
|
-
await runWithConcurrency(metadata2.review.selections.filter((selection) => changedItemIdentifiers.has(selection.itemIdentifier)), TimebackAssessmentRuntimeService.SCORING_CONCURRENCY, async (selection) => {
|
|
106450
|
-
try {
|
|
106451
|
-
const outcome = outcomeByItem.get(selection.itemIdentifier);
|
|
106452
|
-
if (!outcome) {
|
|
106453
|
-
throw new Error(`Completed review omitted item ${selection.itemIdentifier}`);
|
|
106454
|
-
}
|
|
106455
|
-
const childLineItemId = await reviewQuestionLineItemId(result.assessmentLineItem.sourcedId, selection.itemIdentifier);
|
|
106456
|
-
const childResultId = await reviewQuestionResultId(result.sourcedId, childLineItemId);
|
|
106457
|
-
const finalized = buildFinalizedReviewChildResult({
|
|
106458
|
-
childLineItemId,
|
|
106459
|
-
student: result.student,
|
|
106460
|
-
parentAttemptId: result.sourcedId,
|
|
106461
|
-
metadata: metadata2,
|
|
106462
|
-
selection,
|
|
106463
|
-
itemResult: {
|
|
106464
|
-
itemIdentifier: outcome.itemIdentifier,
|
|
106465
|
-
score: outcome.score,
|
|
106466
|
-
isCorrect: outcome.isCorrect
|
|
106467
|
-
},
|
|
106468
|
-
submissionId,
|
|
106469
|
-
timestamp: result.scoreDate
|
|
106470
|
-
});
|
|
106471
|
-
await this.requireClient().api.oneroster.assessmentResults.upsert(childResultId, finalized.resultUpdate);
|
|
106472
|
-
} catch (error88) {
|
|
106473
|
-
addEvent("assessment.review_child_terminal_restore_failed", {
|
|
106474
|
-
"app.assessment.attempt_id": result.sourcedId,
|
|
106475
|
-
"app.assessment.qti_item_identifier": selection.itemIdentifier,
|
|
106476
|
-
"exception.type": errorType(error88),
|
|
106477
|
-
"app.error.message": errorMessage(error88)
|
|
106478
|
-
});
|
|
106479
|
-
}
|
|
106480
|
-
});
|
|
106481
|
-
}
|
|
106482
|
-
reviewItemOutcomes(input) {
|
|
106483
|
-
const resultByItem = new Map(input.itemResults.map((itemResult) => [itemResult.itemIdentifier, itemResult]));
|
|
106484
|
-
const administeredItems = administeredItemIdentifiers(input.metadata.itemSubmissions);
|
|
106485
|
-
return input.metadata.review.selections.filter((selection) => administeredItems.has(selection.itemIdentifier)).map((selection) => {
|
|
106486
|
-
const itemResult = resultByItem.get(selection.itemIdentifier);
|
|
106487
|
-
const submission = itemAdministration(input.metadata.itemSubmissions, selection.itemIdentifier);
|
|
106488
|
-
if (!itemResult || !submission) {
|
|
106489
|
-
throw new Error(`Scoring omitted selected review item ${selection.itemIdentifier}`);
|
|
106490
|
-
}
|
|
106491
|
-
return {
|
|
106492
|
-
itemIdentifier: selection.itemIdentifier,
|
|
106493
|
-
standard: selection.standard,
|
|
106494
|
-
answered: submission.answered,
|
|
106495
|
-
score: itemResult.score,
|
|
106496
|
-
isCorrect: itemResult.isCorrect
|
|
106497
|
-
};
|
|
106498
|
-
});
|
|
106499
|
-
}
|
|
106500
105495
|
async resolveAttemptAuthorization({ gameId, studentId, attemptId, user }, options = {}) {
|
|
106501
105496
|
if (!options.developerAccessValidated) {
|
|
106502
105497
|
await this.deps.validateDeveloperAccess(user, gameId);
|
|
@@ -106525,14 +105520,6 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
106525
105520
|
const enrollment = enrollments.find((candidate) => candidate.id === metadata2.enrollmentId && candidate.course.id === integration.courseId);
|
|
106526
105521
|
return { attempt: { result, metadata: metadata2, integration }, enrollment };
|
|
106527
105522
|
}
|
|
106528
|
-
async authorizeAttempt(params, options = {}) {
|
|
106529
|
-
const authorization = await this.resolveAttemptAuthorization(params, options);
|
|
106530
|
-
return this.requireActiveAttempt(authorization, params.attemptId);
|
|
106531
|
-
}
|
|
106532
|
-
requireActiveAttempt(authorization, attemptId) {
|
|
106533
|
-
const enrollment = requireAssessmentMutationAccess(attemptId, authorization.attempt.integration.status, authorization.enrollment);
|
|
106534
|
-
return { ...authorization.attempt, enrollment };
|
|
106535
|
-
}
|
|
106536
105523
|
async authorizeAttemptRead(params) {
|
|
106537
105524
|
const { attempt, enrollment } = await this.resolveAttemptAuthorization(params);
|
|
106538
105525
|
if ((!enrollment || attempt.integration.status === "deactivated") && !this.isSettled(attempt.result)) {
|
|
@@ -106543,7 +105530,7 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
106543
105530
|
unauthorizedAttempt(attemptId) {
|
|
106544
105531
|
return AssessmentRuntimeError.from(assessmentAttemptUnauthorized(attemptId));
|
|
106545
105532
|
}
|
|
106546
|
-
async snapshotForResult(result, metadata2, integration) {
|
|
105533
|
+
async snapshotForResult(result, metadata2, integration, suppliedTranscript) {
|
|
106547
105534
|
if (isAssessmentAttemptSuperseded({
|
|
106548
105535
|
inProgress: result.inProgress ?? "",
|
|
106549
105536
|
scoreStatus: result.scoreStatus
|
|
@@ -106579,38 +105566,59 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
106579
105566
|
result: buildCompletedAssessmentSubmitResult({ result, metadata: metadata2 })
|
|
106580
105567
|
};
|
|
106581
105568
|
}
|
|
106582
|
-
const
|
|
106583
|
-
|
|
106584
|
-
|
|
106585
|
-
|
|
106586
|
-
|
|
106587
|
-
|
|
106588
|
-
integration,
|
|
106589
|
-
assessment
|
|
106590
|
-
});
|
|
106591
|
-
return this.diagnosticSnapshot(result, metadata2, assessment, integration, routing.state);
|
|
106592
|
-
}
|
|
106593
|
-
return this.snapshot(result, metadata2, assessment, integration);
|
|
106594
|
-
}
|
|
106595
|
-
diagnosticSnapshot(result, metadata2, assessment, integration, state) {
|
|
106596
|
-
return buildDiagnosticAssessmentSnapshot({
|
|
106597
|
-
attemptId: result.sourcedId,
|
|
106598
|
-
metadata: metadata2,
|
|
106599
|
-
assessment,
|
|
106600
|
-
state,
|
|
106601
|
-
activityData: this.activityData(metadata2, assessment, integration)
|
|
105569
|
+
const context2 = this.tokenContext({ result, metadata: metadata2, integration });
|
|
105570
|
+
const graded = await resumeAssessmentTranscript({
|
|
105571
|
+
suppliedTranscript,
|
|
105572
|
+
checkpointTranscript: metadata2.transcript,
|
|
105573
|
+
recover: async () => recoverAssessmentTranscript(context2, await this.studentAssessmentResults(context2.studentId)),
|
|
105574
|
+
grade: (transcript) => this.gradeTranscript(context2, transcript)
|
|
106602
105575
|
});
|
|
105576
|
+
const session2 = await this.assessmentSession(context2, graded.transcript);
|
|
105577
|
+
const common2 = {
|
|
105578
|
+
attemptId: result.sourcedId,
|
|
105579
|
+
assessment: graded.assessment,
|
|
105580
|
+
activityData: this.activityData(metadata2, graded.assessment, integration)
|
|
105581
|
+
};
|
|
105582
|
+
return this.isPlatformRoutedDiagnosticMetadata(graded.metadata) ? {
|
|
105583
|
+
...buildDiagnosticAssessmentSnapshot({
|
|
105584
|
+
...common2,
|
|
105585
|
+
metadata: graded.metadata,
|
|
105586
|
+
state: graded.metadata.diagnostic.state
|
|
105587
|
+
}),
|
|
105588
|
+
...session2
|
|
105589
|
+
} : {
|
|
105590
|
+
...buildConventionalAssessmentSnapshot({ ...common2, metadata: graded.metadata }),
|
|
105591
|
+
...session2
|
|
105592
|
+
};
|
|
105593
|
+
}
|
|
105594
|
+
async assessmentSession(context2, transcript = emptyAssessmentTranscript(context2.attemptId, context2.metadata.selectedTest.contentRevision)) {
|
|
105595
|
+
return { transcript, assessmentToken: await this.deps.mintAssessmentToken(context2) };
|
|
105596
|
+
}
|
|
105597
|
+
async diagnosticSnapshot(result, metadata2, assessment, integration, options) {
|
|
105598
|
+
return {
|
|
105599
|
+
...buildDiagnosticAssessmentSnapshot({
|
|
105600
|
+
attemptId: result.sourcedId,
|
|
105601
|
+
metadata: metadata2,
|
|
105602
|
+
assessment,
|
|
105603
|
+
state: options.state,
|
|
105604
|
+
activityData: this.activityData(metadata2, assessment, integration)
|
|
105605
|
+
}),
|
|
105606
|
+
...await this.assessmentSession(this.tokenContext({ result, metadata: metadata2, integration }))
|
|
105607
|
+
};
|
|
106603
105608
|
}
|
|
106604
105609
|
diagnosticRoutingSnapshot(metadata2, state) {
|
|
106605
105610
|
return projectDiagnosticRoutingSnapshot(metadata2.diagnostic.routingRevision, state);
|
|
106606
105611
|
}
|
|
106607
|
-
snapshot(result, metadata2, assessment, integration) {
|
|
106608
|
-
return
|
|
106609
|
-
|
|
106610
|
-
|
|
106611
|
-
|
|
106612
|
-
|
|
106613
|
-
|
|
105612
|
+
async snapshot(result, metadata2, assessment, integration) {
|
|
105613
|
+
return {
|
|
105614
|
+
...buildConventionalAssessmentSnapshot({
|
|
105615
|
+
attemptId: result.sourcedId,
|
|
105616
|
+
metadata: metadata2,
|
|
105617
|
+
assessment,
|
|
105618
|
+
activityData: this.activityData(metadata2, assessment, integration)
|
|
105619
|
+
}),
|
|
105620
|
+
...await this.assessmentSession(this.tokenContext({ result, metadata: metadata2, integration }))
|
|
105621
|
+
};
|
|
106614
105622
|
}
|
|
106615
105623
|
activityData(metadata2, assessment, integration) {
|
|
106616
105624
|
const course = this.trackableCourse(integration, "assessment.activity_tracking_invalid_course_metadata");
|
|
@@ -106671,16 +105679,6 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
106671
105679
|
isSettled(result) {
|
|
106672
105680
|
return this.isAwaitingAward(result) || this.isCompleted(result);
|
|
106673
105681
|
}
|
|
106674
|
-
assertInProgress(result) {
|
|
106675
|
-
if (!this.isOpen(result)) {
|
|
106676
|
-
throw AssessmentRuntimeError.from(assessmentAttemptNotOpen(result.sourcedId));
|
|
106677
|
-
}
|
|
106678
|
-
}
|
|
106679
|
-
assertResponseVersion(metadata2, expectedResponseVersion) {
|
|
106680
|
-
if (metadata2.responseVersion !== expectedResponseVersion) {
|
|
106681
|
-
throw AssessmentRuntimeError.from(assessmentResponseVersionConflict(expectedResponseVersion, metadata2.responseVersion));
|
|
106682
|
-
}
|
|
106683
|
-
}
|
|
106684
105682
|
scoreFromResult(result, possible, metadata2) {
|
|
106685
105683
|
return metadata2.score ?? assessmentScoreFromPercentage(result.score, possible);
|
|
106686
105684
|
}
|
|
@@ -106731,7 +105729,22 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
106731
105729
|
}
|
|
106732
105730
|
return projectReviewAssessment(playable, preparedReviewBankIndex(plan), plan.review.selections);
|
|
106733
105731
|
}
|
|
106734
|
-
async
|
|
105732
|
+
async claimPreparedAttempt(db2, plan, assessment, integration) {
|
|
105733
|
+
const occupant = await this.findAttemptResult(plan.attemptId);
|
|
105734
|
+
if (occupant) {
|
|
105735
|
+
const metadata3 = playcademyAssessmentResultMetadata(occupant.metadata);
|
|
105736
|
+
if (occupant.status !== ONEROSTER_STATUS2.active || !metadata3 || !this.attemptInScope(occupant, metadata3, plan) || metadata3.enrollmentId !== plan.enrollmentId || !this.isOpen(occupant) || !this.attemptMatchesPlan(metadata3, plan)) {
|
|
105737
|
+
return null;
|
|
105738
|
+
}
|
|
105739
|
+
addEvent("assessment.prepared_attempt_recovered", {
|
|
105740
|
+
"app.assessment.attempt_id": plan.attemptId
|
|
105741
|
+
});
|
|
105742
|
+
const [snapshot2] = await Promise.all([
|
|
105743
|
+
this.snapshotForResult(occupant, metadata3, integration),
|
|
105744
|
+
this.supersedeAttemptsBestEffort(db2, new Set(plan.supersedes))
|
|
105745
|
+
]);
|
|
105746
|
+
return snapshot2;
|
|
105747
|
+
}
|
|
106735
105748
|
const timestamp6 = new Date().toISOString();
|
|
106736
105749
|
const metadata2 = buildPreparedAssessmentMetadata(plan, timestamp6, assessment.title);
|
|
106737
105750
|
const result = await this.requireClient().api.oneroster.assessmentResults.upsert(plan.attemptId, {
|
|
@@ -106748,7 +105761,42 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
106748
105761
|
grade: integration.grade
|
|
106749
105762
|
})
|
|
106750
105763
|
});
|
|
106751
|
-
|
|
105764
|
+
const [snapshot] = await Promise.all([
|
|
105765
|
+
this.isPlatformRoutedDiagnosticMetadata(metadata2) ? this.diagnosticSnapshot(result, metadata2, assessment, integration, {
|
|
105766
|
+
state: metadata2.diagnostic.state
|
|
105767
|
+
}) : this.snapshot(result, metadata2, assessment, integration),
|
|
105768
|
+
this.supersedeAttemptsBestEffort(db2, new Set(plan.supersedes))
|
|
105769
|
+
]);
|
|
105770
|
+
return snapshot;
|
|
105771
|
+
}
|
|
105772
|
+
attemptMatchesPlan(metadata2, plan) {
|
|
105773
|
+
const planned = buildPreparedAssessmentMetadata(plan, metadata2.startedAt, "");
|
|
105774
|
+
if (metadata2.selectedTest.assessmentKey !== planned.selectedTest.assessmentKey || metadata2.selectedTest.identifier !== planned.selectedTest.identifier || metadata2.selectedTest.contentRevision !== planned.selectedTest.contentRevision) {
|
|
105775
|
+
return false;
|
|
105776
|
+
}
|
|
105777
|
+
if (metadata2.purpose === "review" && planned.purpose === "review") {
|
|
105778
|
+
return metadata2.review.requestFingerprint === planned.review.requestFingerprint && metadata2.review.bankRevision === planned.review.bankRevision && metadata2.review.selections.length === planned.review.selections.length && metadata2.review.selections.every((selection, index2) => selection.itemIdentifier === planned.review.selections[index2].itemIdentifier);
|
|
105779
|
+
}
|
|
105780
|
+
if (metadata2.purpose === "mastery" && planned.purpose === "mastery") {
|
|
105781
|
+
return metadata2.mastery.requestFingerprint === planned.mastery.requestFingerprint;
|
|
105782
|
+
}
|
|
105783
|
+
if (metadata2.purpose === "diagnostic" && plan.purpose === "diagnostic") {
|
|
105784
|
+
return this.isPlatformRoutedDiagnosticMetadata(metadata2) && metadata2.diagnostic.definitionId === plan.diagnostic.definitionId && metadata2.diagnostic.routingRevision === plan.diagnostic.routingRevision;
|
|
105785
|
+
}
|
|
105786
|
+
return metadata2.purpose === planned.purpose;
|
|
105787
|
+
}
|
|
105788
|
+
async findAttemptResult(attemptId) {
|
|
105789
|
+
try {
|
|
105790
|
+
return await this.requireClient().api.oneroster.assessmentResults.get(attemptId);
|
|
105791
|
+
} catch (error88) {
|
|
105792
|
+
if (isApiError(error88) && error88.statusCode === 404) {
|
|
105793
|
+
return null;
|
|
105794
|
+
}
|
|
105795
|
+
throw error88;
|
|
105796
|
+
}
|
|
105797
|
+
}
|
|
105798
|
+
attemptInScope(result, metadata2, scope) {
|
|
105799
|
+
return metadata2.activityId === scope.activityId && metadata2.purpose === scope.purpose && metadata2.courseId === scope.courseId && metadata2.integrationId === scope.integrationId && result.student.sourcedId === scope.studentId;
|
|
106752
105800
|
}
|
|
106753
105801
|
async claimAttemptId(scope, from) {
|
|
106754
105802
|
for (let attemptNumber = from;attemptNumber < from + 20; attemptNumber += 1) {
|
|
@@ -106764,15 +105812,7 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
106764
105812
|
throw new Error(`Assessment attempt numbers ${from} through ${from + 19} are all occupied by records this code cannot read; refusing to overwrite them.`);
|
|
106765
105813
|
}
|
|
106766
105814
|
async attemptIdOccupied(attemptId) {
|
|
106767
|
-
|
|
106768
|
-
await this.requireClient().api.oneroster.assessmentResults.get(attemptId);
|
|
106769
|
-
} catch (error88) {
|
|
106770
|
-
if (isApiError(error88) && error88.statusCode === 404) {
|
|
106771
|
-
return false;
|
|
106772
|
-
}
|
|
106773
|
-
throw error88;
|
|
106774
|
-
}
|
|
106775
|
-
return true;
|
|
105815
|
+
return await this.findAttemptResult(attemptId) !== null;
|
|
106776
105816
|
}
|
|
106777
105817
|
replayCompletion(attempt, submissionId, params, game2) {
|
|
106778
105818
|
const { score, completion } = attempt.metadata;
|
|
@@ -106793,18 +105833,6 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
106793
105833
|
game: game2
|
|
106794
105834
|
};
|
|
106795
105835
|
}
|
|
106796
|
-
async putResultMetadata(result, metadata2) {
|
|
106797
|
-
await this.requireClient().api.oneroster.assessmentResults.upsert(result.sourcedId, {
|
|
106798
|
-
status: ONEROSTER_STATUS2.active,
|
|
106799
|
-
assessmentLineItem: result.assessmentLineItem,
|
|
106800
|
-
student: result.student,
|
|
106801
|
-
score: result.score ?? 0,
|
|
106802
|
-
scoreDate: result.scoreDate,
|
|
106803
|
-
scoreStatus: result.scoreStatus,
|
|
106804
|
-
...result.inProgress === undefined || result.inProgress === null ? {} : { inProgress: result.inProgress },
|
|
106805
|
-
metadata: mergeAssessmentRuntimeMetadata(result.metadata, metadata2)
|
|
106806
|
-
});
|
|
106807
|
-
}
|
|
106808
105836
|
async emitCompletionBestEffort(input) {
|
|
106809
105837
|
const { attempt } = input;
|
|
106810
105838
|
const integration = attempt.integration;
|
|
@@ -110393,6 +109421,8 @@ function createPlatformServices(deps) {
|
|
|
110393
109421
|
mintPlatformServiceToken,
|
|
110394
109422
|
mintAssessmentPreparationReceipt,
|
|
110395
109423
|
verifyAssessmentPreparationReceipt,
|
|
109424
|
+
mintAssessmentToken,
|
|
109425
|
+
verifyAssessmentToken,
|
|
110396
109426
|
validateDeveloperAccessBySlug,
|
|
110397
109427
|
validateDeveloperAccess,
|
|
110398
109428
|
validateGameManagementAccess
|
|
@@ -110467,7 +109497,9 @@ function createPlatformServices(deps) {
|
|
|
110467
109497
|
validateDeveloperAccess,
|
|
110468
109498
|
assessmentRuntimeLock,
|
|
110469
109499
|
mintAssessmentPreparationReceipt,
|
|
110470
|
-
verifyAssessmentPreparationReceipt
|
|
109500
|
+
verifyAssessmentPreparationReceipt,
|
|
109501
|
+
mintAssessmentToken,
|
|
109502
|
+
verifyAssessmentToken
|
|
110471
109503
|
});
|
|
110472
109504
|
return {
|
|
110473
109505
|
assessmentAssets,
|
|
@@ -111217,6 +110249,8 @@ function createServices(ctx) {
|
|
|
111217
110249
|
mintPlatformServiceToken: auth2.mintPlatformServiceToken.bind(auth2),
|
|
111218
110250
|
mintAssessmentPreparationReceipt: auth2.mintAssessmentPreparationReceipt.bind(auth2),
|
|
111219
110251
|
verifyAssessmentPreparationReceipt: auth2.verifyAssessmentPreparationReceipt.bind(auth2),
|
|
110252
|
+
mintAssessmentToken: auth2.mintAssessmentToken.bind(auth2),
|
|
110253
|
+
verifyAssessmentToken: auth2.verifyAssessmentToken.bind(auth2),
|
|
111220
110254
|
...gaming.validators
|
|
111221
110255
|
});
|
|
111222
110256
|
const standalone = createStandaloneServices({ db: db2, auth: auth2, timeback: timeback2 });
|
|
@@ -111745,6 +110779,15 @@ function createSandboxAuthProvider() {
|
|
|
111745
110779
|
});
|
|
111746
110780
|
return { receipt, expiresAt: new Date(expiresAt).toISOString() };
|
|
111747
110781
|
},
|
|
110782
|
+
async mintAssessmentToken(context2) {
|
|
110783
|
+
const token = `sandbox_assessment_${crypto.randomUUID()}`;
|
|
110784
|
+
sandboxAssessmentTokens.set(token, structuredClone(context2));
|
|
110785
|
+
return token;
|
|
110786
|
+
},
|
|
110787
|
+
async verifyAssessmentToken(token) {
|
|
110788
|
+
const context2 = sandboxAssessmentTokens.get(token);
|
|
110789
|
+
return context2 ? structuredClone(context2) : null;
|
|
110790
|
+
},
|
|
111748
110791
|
async verifyAssessmentPreparationReceipt(receipt) {
|
|
111749
110792
|
const prepared = sandboxAssessmentPreparationReceipts.get(receipt);
|
|
111750
110793
|
if (!prepared) {
|
|
@@ -111797,13 +110840,18 @@ function createSandboxAuthProvider() {
|
|
|
111797
110840
|
}
|
|
111798
110841
|
};
|
|
111799
110842
|
}
|
|
111800
|
-
var sandboxApiKeys, sandboxAssessmentPreparationReceipts, ASSESSMENT_PREPARATION_RECEIPT_LIFETIME_MS;
|
|
111801
|
-
var init_auth_provider = __esm(() => {
|
|
110843
|
+
var sandboxApiKeys, sandboxAssessmentPreparationReceipts, ASSESSMENT_PREPARATION_RECEIPT_LIFETIME_MS, sandboxAssessmentTokens;
|
|
110844
|
+
var init_auth_provider = __esm(async () => {
|
|
111802
110845
|
init_src6();
|
|
111803
110846
|
init_constants();
|
|
110847
|
+
await init_dist5();
|
|
111804
110848
|
sandboxApiKeys = new Map;
|
|
111805
110849
|
sandboxAssessmentPreparationReceipts = new Map;
|
|
111806
110850
|
ASSESSMENT_PREPARATION_RECEIPT_LIFETIME_MS = 5 * 60 * 1000;
|
|
110851
|
+
sandboxAssessmentTokens = new TimebackCache({
|
|
110852
|
+
defaultTTL: 2 * 60 * 60 * 1000,
|
|
110853
|
+
maxSize: 1000
|
|
110854
|
+
});
|
|
111807
110855
|
});
|
|
111808
110856
|
|
|
111809
110857
|
// src/infrastructure/api/providers/cache.provider.ts
|
|
@@ -111964,10 +111012,10 @@ var init_storage_provider = __esm(() => {
|
|
|
111964
111012
|
});
|
|
111965
111013
|
|
|
111966
111014
|
// src/infrastructure/api/providers/index.ts
|
|
111967
|
-
var init_providers = __esm(() => {
|
|
111968
|
-
init_auth_provider();
|
|
111015
|
+
var init_providers = __esm(async () => {
|
|
111969
111016
|
init_cache_provider();
|
|
111970
111017
|
init_storage_provider();
|
|
111018
|
+
await init_auth_provider();
|
|
111971
111019
|
});
|
|
111972
111020
|
|
|
111973
111021
|
// src/infrastructure/api/context.ts
|
|
@@ -112037,10 +111085,10 @@ var init_context = __esm(async () => {
|
|
|
112037
111085
|
init_config2();
|
|
112038
111086
|
init_utils7();
|
|
112039
111087
|
init_src6();
|
|
112040
|
-
init_providers();
|
|
112041
111088
|
await __promiseAll([
|
|
112042
111089
|
init_factory(),
|
|
112043
|
-
init_clients()
|
|
111090
|
+
init_clients(),
|
|
111091
|
+
init_providers()
|
|
112044
111092
|
]);
|
|
112045
111093
|
});
|
|
112046
111094
|
|
|
@@ -168436,7 +167484,7 @@ function parseQtiLibraryParams(searchParams) {
|
|
|
168436
167484
|
limit
|
|
168437
167485
|
};
|
|
168438
167486
|
}
|
|
168439
|
-
var populateStudent, getUser, getUserEnrollments, getUserById, setupIntegration, getIntegrations, getRemovedIntegrations, createIntegration, updateIntegration, deactivateCourse, reactivateCourse, getIntegrationConfig, verifyIntegration, getConfig, deleteIntegrations, endActivity, heartbeat, advanceCourse, unenrollCourse, prepareRuntimeAssessment, startRuntimeAssessment, getRuntimeAssessment, getLatestRuntimeAssessment, saveRuntimeAssessment, submitRuntimeAssessmentItem, submitRuntimeAssessment, finalizeRuntimeAssessment, exportRuntimeAssessmentFixture, getStudentXp, getStudentMastery, getStudentHighestGradeMastered, getRoster, getStudentOverview, getGameMetrics, getStudentActivity, getGradeLevelTestResults, getGradeLevelTestReview, getActivityDetail, listMetricDiscrepancies, verifyMetricDiscrepancy, grantXp, adjustTime, adjustMastery, reconcileMasteryForConfigChange, searchStudents, enrollStudent, unenrollStudent, reactivateEnrollment, listAssessments, createAssessment, attachExistingAssessments, updateAssessment, synchronizeReviewBank, reorderQuestions, removeAssessment, listQuestions, listQuestionLibrary, listTestLibrary, copyAssessment, createQuestion, updateQuestion, removeQuestion, timeback2;
|
|
167487
|
+
var populateStudent, getUser, getUserEnrollments, getUserById, setupIntegration, getIntegrations, getRemovedIntegrations, createIntegration, updateIntegration, deactivateCourse, reactivateCourse, getIntegrationConfig, verifyIntegration, getConfig, deleteIntegrations, endActivity, heartbeat, advanceCourse, unenrollCourse, prepareRuntimeAssessment, startRuntimeAssessment, getRuntimeAssessment, getLatestRuntimeAssessment, resumeRuntimeAssessment, saveRuntimeAssessment, recordRuntimeAssessmentProgress, submitRuntimeAssessmentItem, submitRuntimeAssessment, finalizeRuntimeAssessment, exportRuntimeAssessmentFixture, getStudentXp, getStudentMastery, getStudentHighestGradeMastered, getRoster, getStudentOverview, getGameMetrics, getStudentActivity, getGradeLevelTestResults, getGradeLevelTestReview, getActivityDetail, listMetricDiscrepancies, verifyMetricDiscrepancy, grantXp, adjustTime, adjustMastery, reconcileMasteryForConfigChange, searchStudents, enrollStudent, unenrollStudent, reactivateEnrollment, listAssessments, createAssessment, attachExistingAssessments, updateAssessment, synchronizeReviewBank, reorderQuestions, removeAssessment, listQuestions, listQuestionLibrary, listTestLibrary, copyAssessment, createQuestion, updateQuestion, removeQuestion, timeback2;
|
|
168440
167488
|
var init_timeback_controller = __esm(() => {
|
|
168441
167489
|
init_esm();
|
|
168442
167490
|
init_src();
|
|
@@ -168738,6 +167786,20 @@ var init_timeback_controller = __esm(() => {
|
|
|
168738
167786
|
user: ctx.user
|
|
168739
167787
|
});
|
|
168740
167788
|
});
|
|
167789
|
+
resumeRuntimeAssessment = requireDeveloper(async (ctx) => {
|
|
167790
|
+
const attemptId = ctx.params.attemptId;
|
|
167791
|
+
const body2 = await parseRequestBody(ctx.request, ResumeRuntimeAssessmentRequestSchema);
|
|
167792
|
+
if (!attemptId) {
|
|
167793
|
+
throw ApiError.badRequest("Missing attemptId");
|
|
167794
|
+
}
|
|
167795
|
+
return ctx.services.timebackAssessmentRuntime.resume({
|
|
167796
|
+
gameId: body2.gameId,
|
|
167797
|
+
studentId: body2.studentId,
|
|
167798
|
+
attemptId,
|
|
167799
|
+
input: { transcript: body2.transcript },
|
|
167800
|
+
user: ctx.user
|
|
167801
|
+
});
|
|
167802
|
+
});
|
|
168741
167803
|
saveRuntimeAssessment = requireDeveloper(async (ctx) => {
|
|
168742
167804
|
const attemptId = ctx.params.attemptId;
|
|
168743
167805
|
const body2 = await parseRequestBody(ctx.request, SaveRuntimeAssessmentRequestSchema);
|
|
@@ -168750,7 +167812,26 @@ var init_timeback_controller = __esm(() => {
|
|
|
168750
167812
|
attemptId,
|
|
168751
167813
|
input: {
|
|
168752
167814
|
expectedResponseVersion: body2.expectedResponseVersion,
|
|
168753
|
-
|
|
167815
|
+
transcript: body2.transcript
|
|
167816
|
+
},
|
|
167817
|
+
user: ctx.user
|
|
167818
|
+
});
|
|
167819
|
+
});
|
|
167820
|
+
recordRuntimeAssessmentProgress = requireDeveloper(async (ctx) => {
|
|
167821
|
+
const attemptId = ctx.params.attemptId;
|
|
167822
|
+
const body2 = await parseRequestBody(ctx.request, RecordRuntimeAssessmentProgressRequestSchema);
|
|
167823
|
+
if (!attemptId) {
|
|
167824
|
+
throw ApiError.badRequest("Missing attemptId");
|
|
167825
|
+
}
|
|
167826
|
+
return ctx.services.timebackAssessmentRuntime.recordProgress({
|
|
167827
|
+
gameId: body2.gameId,
|
|
167828
|
+
studentId: body2.studentId,
|
|
167829
|
+
attemptId,
|
|
167830
|
+
input: {
|
|
167831
|
+
assessmentToken: body2.assessmentToken,
|
|
167832
|
+
transcript: body2.transcript,
|
|
167833
|
+
expectedResponseVersion: body2.expectedResponseVersion,
|
|
167834
|
+
itemIdentifier: body2.itemIdentifier
|
|
168754
167835
|
},
|
|
168755
167836
|
user: ctx.user
|
|
168756
167837
|
});
|
|
@@ -168766,6 +167847,8 @@ var init_timeback_controller = __esm(() => {
|
|
|
168766
167847
|
studentId: body2.studentId,
|
|
168767
167848
|
attemptId,
|
|
168768
167849
|
input: {
|
|
167850
|
+
assessmentToken: body2.assessmentToken,
|
|
167851
|
+
transcript: body2.transcript,
|
|
168769
167852
|
expectedResponseVersion: body2.expectedResponseVersion,
|
|
168770
167853
|
submissionId: body2.submissionId,
|
|
168771
167854
|
itemIdentifier: body2.itemIdentifier,
|
|
@@ -168788,6 +167871,7 @@ var init_timeback_controller = __esm(() => {
|
|
|
168788
167871
|
input: {
|
|
168789
167872
|
expectedResponseVersion: body2.expectedResponseVersion,
|
|
168790
167873
|
submissionId: body2.submissionId,
|
|
167874
|
+
transcript: body2.transcript,
|
|
168791
167875
|
...body2.session ? { session: body2.session } : {}
|
|
168792
167876
|
},
|
|
168793
167877
|
game: {
|
|
@@ -169265,6 +168349,8 @@ var init_timeback_controller = __esm(() => {
|
|
|
169265
168349
|
getLatestRuntimeAssessment,
|
|
169266
168350
|
getRuntimeAssessment,
|
|
169267
168351
|
saveRuntimeAssessment,
|
|
168352
|
+
resumeRuntimeAssessment,
|
|
168353
|
+
recordRuntimeAssessmentProgress,
|
|
169268
168354
|
submitRuntimeAssessmentItem,
|
|
169269
168355
|
submitRuntimeAssessment,
|
|
169270
168356
|
finalizeRuntimeAssessment,
|
|
@@ -170257,6 +169343,8 @@ var init_timeback8 = __esm(async () => {
|
|
|
170257
169343
|
timebackRouter.post("/assessments/start", handle2(timeback2.startRuntimeAssessment));
|
|
170258
169344
|
timebackRouter.get("/assessments/latest", handle2(timeback2.getLatestRuntimeAssessment));
|
|
170259
169345
|
timebackRouter.get("/assessments/:attemptId", handle2(timeback2.getRuntimeAssessment));
|
|
169346
|
+
timebackRouter.post("/assessments/:attemptId/resume", handle2(timeback2.resumeRuntimeAssessment));
|
|
169347
|
+
timebackRouter.post("/assessments/:attemptId/progress", handle2(timeback2.recordRuntimeAssessmentProgress));
|
|
170260
169348
|
timebackRouter.post("/assessments/:attemptId/save", handle2(timeback2.saveRuntimeAssessment));
|
|
170261
169349
|
timebackRouter.post("/assessments/:attemptId/submit-item", handle2(timeback2.submitRuntimeAssessmentItem));
|
|
170262
169350
|
timebackRouter.post("/assessments/:attemptId/submit", handle2(timeback2.submitRuntimeAssessment));
|
|
@@ -170665,13 +169753,13 @@ var init_server = __esm(async () => {
|
|
|
170665
169753
|
init_port();
|
|
170666
169754
|
init_package();
|
|
170667
169755
|
init_config();
|
|
170668
|
-
init_providers();
|
|
170669
169756
|
init_app();
|
|
170670
169757
|
init_database2();
|
|
170671
169758
|
init_options();
|
|
170672
169759
|
await __promiseAll([
|
|
170673
169760
|
init_api3(),
|
|
170674
|
-
init_context()
|
|
169761
|
+
init_context(),
|
|
169762
|
+
init_providers()
|
|
170675
169763
|
]);
|
|
170676
169764
|
version5 = package_default.version;
|
|
170677
169765
|
});
|