@playcademy/sandbox 0.8.1-beta.6 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +251 -392
- package/dist/server.js +251 -392
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1128,7 +1128,7 @@ var package_default;
|
|
|
1128
1128
|
var init_package = __esm(() => {
|
|
1129
1129
|
package_default = {
|
|
1130
1130
|
name: "@playcademy/sandbox",
|
|
1131
|
-
version: "0.
|
|
1131
|
+
version: "0.9.0",
|
|
1132
1132
|
description: "Local development server for Playcademy game development",
|
|
1133
1133
|
type: "module",
|
|
1134
1134
|
exports: {
|
|
@@ -10748,12 +10748,10 @@ function selectAssessmentCourseCandidate(candidates, subjectSpecified) {
|
|
|
10748
10748
|
if (candidates.length === 0) {
|
|
10749
10749
|
return { outcome: "none" };
|
|
10750
10750
|
}
|
|
10751
|
-
const
|
|
10752
|
-
if (
|
|
10751
|
+
const eligible = candidates.filter((candidate) => candidate.hasLiveTests);
|
|
10752
|
+
if (eligible.length === 0) {
|
|
10753
10753
|
return { outcome: "no_eligible_tests" };
|
|
10754
10754
|
}
|
|
10755
|
-
const withOpenAttempt = serviceable.filter((candidate) => candidate.hasOpenAttempt);
|
|
10756
|
-
const eligible = withOpenAttempt.length > 0 ? withOpenAttempt : serviceable;
|
|
10757
10755
|
const subjects = new Set(eligible.map((candidate) => candidate.subject)).size;
|
|
10758
10756
|
if (!subjectSpecified && subjects > 1) {
|
|
10759
10757
|
return { outcome: "ambiguous", subjects };
|
|
@@ -10765,12 +10763,9 @@ function assessmentAttemptTimestamp(value) {
|
|
|
10765
10763
|
const parsed = Date.parse(value);
|
|
10766
10764
|
return Number.isNaN(parsed) ? 0 : parsed;
|
|
10767
10765
|
}
|
|
10768
|
-
function newestAttempt(left, right, field) {
|
|
10769
|
-
const dateDifference = assessmentAttemptTimestamp(right[field]) - assessmentAttemptTimestamp(left[field]);
|
|
10770
|
-
return dateDifference || left.attemptId.localeCompare(right.attemptId);
|
|
10771
|
-
}
|
|
10772
10766
|
function compareNewestCompletedAssessmentAttempt(left, right) {
|
|
10773
|
-
|
|
10767
|
+
const dateDifference = assessmentAttemptTimestamp(right.scoreDate) - assessmentAttemptTimestamp(left.scoreDate);
|
|
10768
|
+
return dateDifference || left.attemptId.localeCompare(right.attemptId);
|
|
10774
10769
|
}
|
|
10775
10770
|
function compareAssessmentCatalogOrder(left, right) {
|
|
10776
10771
|
if (left.assessmentKey !== null && right.assessmentKey !== null) {
|
|
@@ -10793,31 +10788,22 @@ function orderRuntimeAssessmentTests(tests) {
|
|
|
10793
10788
|
return [...tests].toSorted(compareAssessmentCatalogOrder);
|
|
10794
10789
|
}
|
|
10795
10790
|
function selectRuntimeAssessment(liveTests, attempts) {
|
|
10796
|
-
const resumable = attempts.filter((attempt) => attempt.resumable !== false && (isAssessmentAttemptOpen(attempt) || isAssessmentAttemptAwaitingAward(attempt))).toSorted((left, right) => newestAttempt(left, right, "updatedAt"));
|
|
10797
|
-
if (resumable.length > 0) {
|
|
10798
|
-
return {
|
|
10799
|
-
kind: "resume",
|
|
10800
|
-
attempt: resumable[0],
|
|
10801
|
-
additionalResumableAttemptIds: resumable.slice(1).map((candidate) => candidate.attemptId)
|
|
10802
|
-
};
|
|
10803
|
-
}
|
|
10804
10791
|
const orderedTests = orderRuntimeAssessmentTests(liveTests);
|
|
10805
10792
|
if (orderedTests.length === 0) {
|
|
10806
10793
|
return null;
|
|
10807
10794
|
}
|
|
10808
|
-
const
|
|
10809
|
-
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));
|
|
10810
10797
|
const unattempted = orderedTests.find((test) => !exposedAssessmentKeys.has(test.assessmentKey));
|
|
10811
10798
|
if (unattempted) {
|
|
10812
|
-
return {
|
|
10799
|
+
return { test: unattempted, reason: "unattempted" };
|
|
10813
10800
|
}
|
|
10814
|
-
const
|
|
10815
|
-
const latestTestIndex =
|
|
10801
|
+
const latest = history.toSorted(compareNewestCompletedAssessmentAttempt)[0];
|
|
10802
|
+
const latestTestIndex = latest ? orderedTests.findIndex((test) => test.assessmentKey === latest.selectedAssessmentKey) : -1;
|
|
10816
10803
|
if (latestTestIndex === -1) {
|
|
10817
|
-
return {
|
|
10804
|
+
return { test: orderedTests[0], reason: "first_live" };
|
|
10818
10805
|
}
|
|
10819
10806
|
return {
|
|
10820
|
-
kind: "start",
|
|
10821
10807
|
test: orderedTests[(latestTestIndex + 1) % orderedTests.length],
|
|
10822
10808
|
reason: "round_robin"
|
|
10823
10809
|
};
|
|
@@ -100352,13 +100338,6 @@ function buildAssessmentAwardRecord(input, mastery) {
|
|
|
100352
100338
|
...mastery.warnings ? { warnings: mastery.warnings } : {}
|
|
100353
100339
|
};
|
|
100354
100340
|
}
|
|
100355
|
-
function stageAssessmentAttemptSupersession(attempt) {
|
|
100356
|
-
Object.assign(attempt.result, ASSESSMENT_ATTEMPT_SUPERSEDED);
|
|
100357
|
-
Object.assign(attempt.selection, ASSESSMENT_ATTEMPT_SUPERSEDED);
|
|
100358
|
-
}
|
|
100359
|
-
function shouldConfirmAssessmentVersionUnavailable(details) {
|
|
100360
|
-
return typeof details === "object" && details !== null && "expectedRevision" in details && typeof details.expectedRevision === "string" && !("currentRevision" in details);
|
|
100361
|
-
}
|
|
100362
100341
|
function buildAssessmentAttemptSupersessionUpdate(result) {
|
|
100363
100342
|
if (result.status !== "active" || !isAssessmentAttemptOpen({
|
|
100364
100343
|
inProgress: result.inProgress ?? "",
|
|
@@ -100376,17 +100355,6 @@ function buildAssessmentAttemptSupersessionUpdate(result) {
|
|
|
100376
100355
|
metadata: result.metadata ?? {}
|
|
100377
100356
|
};
|
|
100378
100357
|
}
|
|
100379
|
-
async function resolveAvailableAssessmentAttempt(initialDecision, select3, resume) {
|
|
100380
|
-
let decision = initialDecision;
|
|
100381
|
-
while (decision?.kind === "resume") {
|
|
100382
|
-
const snapshot = await resume(decision);
|
|
100383
|
-
if (snapshot !== null) {
|
|
100384
|
-
return { kind: "resumed", snapshot };
|
|
100385
|
-
}
|
|
100386
|
-
decision = select3();
|
|
100387
|
-
}
|
|
100388
|
-
return { kind: "continue", decision };
|
|
100389
|
-
}
|
|
100390
100358
|
function diagnosticQuestionLineItemId(parentLineItemId, itemIdentifier) {
|
|
100391
100359
|
return deterministicUUID(["playcademy:diagnostic-question-line-item:v1", parentLineItemId, itemIdentifier].join("\x00"));
|
|
100392
100360
|
}
|
|
@@ -100461,6 +100429,20 @@ function assessmentAttemptId(input) {
|
|
|
100461
100429
|
String(input.attemptNumber)
|
|
100462
100430
|
].join("\x00"));
|
|
100463
100431
|
}
|
|
100432
|
+
function buildAssessmentAttemptResultWhere(scope) {
|
|
100433
|
+
return {
|
|
100434
|
+
"student.sourcedId": scope.studentId,
|
|
100435
|
+
status: ONEROSTER_STATUS2.active,
|
|
100436
|
+
[`metadata.${PLAYCADEMY_ASSESSMENT_RESULT_METADATA_KEY}.activityId`]: scope.activityId
|
|
100437
|
+
};
|
|
100438
|
+
}
|
|
100439
|
+
function buildAssessmentChildResultWhere(scope) {
|
|
100440
|
+
return {
|
|
100441
|
+
"student.sourcedId": scope.studentId,
|
|
100442
|
+
status: ONEROSTER_STATUS2.active,
|
|
100443
|
+
[`metadata.${PLAYCADEMY_ASSESSMENT_ITEM_RESULT_METADATA_KEY}.payload.attempt.activityId`]: scope.activityId
|
|
100444
|
+
};
|
|
100445
|
+
}
|
|
100464
100446
|
async function findLatestAssessmentResult(results, match) {
|
|
100465
100447
|
let latest = null;
|
|
100466
100448
|
let latestTimestamp = 0;
|
|
@@ -100900,6 +100882,7 @@ var PLAYABLE_SHAPE_SET;
|
|
|
100900
100882
|
var init_timeback_assessment_runtime_util = __esm(() => {
|
|
100901
100883
|
init_src();
|
|
100902
100884
|
init_assessment_runtime2();
|
|
100885
|
+
init_constants3();
|
|
100903
100886
|
init_qti();
|
|
100904
100887
|
init_types2();
|
|
100905
100888
|
init_utils6();
|
|
@@ -102687,21 +102670,6 @@ function assessmentPreparationFlightKey(input) {
|
|
|
102687
102670
|
async function assessmentPreparationReceiptMatches(plan, request) {
|
|
102688
102671
|
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);
|
|
102689
102672
|
}
|
|
102690
|
-
function resolveAssessmentPreparationSelection(decision, candidateCourseCount) {
|
|
102691
|
-
if (decision?.kind === "resume") {
|
|
102692
|
-
return {
|
|
102693
|
-
status: "blocked",
|
|
102694
|
-
result: { status: "canonical_required", reason: "resumable_attempt" }
|
|
102695
|
-
};
|
|
102696
|
-
}
|
|
102697
|
-
if (candidateCourseCount !== 1) {
|
|
102698
|
-
return {
|
|
102699
|
-
status: "blocked",
|
|
102700
|
-
result: { status: "unprepared", reason: "prerequisite_unavailable" }
|
|
102701
|
-
};
|
|
102702
|
-
}
|
|
102703
|
-
return decision ? { status: "ready", decision } : { status: "unavailable" };
|
|
102704
|
-
}
|
|
102705
102673
|
function preparedAssessmentIntegration(plan, candidates) {
|
|
102706
102674
|
if (candidates.rows.length !== 1) {
|
|
102707
102675
|
return null;
|
|
@@ -103447,15 +103415,16 @@ function assessmentReviewTranscriptExposures(input) {
|
|
|
103447
103415
|
for (const { result, metadata: metadata2 } of input.attempts) {
|
|
103448
103416
|
const state = { ...result, inProgress: result.inProgress ?? "" };
|
|
103449
103417
|
const settled = isAssessmentAttemptAwaitingAward(state) || isAssessmentAttemptCompleted(state);
|
|
103450
|
-
|
|
103418
|
+
const useTranscript = !settled && metadata2.transcript !== undefined;
|
|
103419
|
+
if (metadata2.purpose === "review" && (useTranscript || settled)) {
|
|
103451
103420
|
authoritative.add(result.sourcedId);
|
|
103452
103421
|
if (metadata2.review.bankRevision === input.bankRevision) {
|
|
103453
|
-
const accepted =
|
|
103454
|
-
itemIdentifier: submission.itemIdentifier,
|
|
103455
|
-
deliveredAt: submission.submittedAt
|
|
103456
|
-
})) : metadata2.transcript.administrations.map((entry) => ({
|
|
103422
|
+
const accepted = useTranscript ? metadata2.transcript.administrations.map((entry) => ({
|
|
103457
103423
|
itemIdentifier: entry.itemIdentifier,
|
|
103458
103424
|
deliveredAt: metadata2.updatedAt
|
|
103425
|
+
})) : metadata2.itemSubmissions.map((submission) => ({
|
|
103426
|
+
itemIdentifier: submission.itemIdentifier,
|
|
103427
|
+
deliveredAt: submission.submittedAt
|
|
103459
103428
|
}));
|
|
103460
103429
|
exposures.push(...accepted);
|
|
103461
103430
|
}
|
|
@@ -103589,7 +103558,6 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
103589
103558
|
static PREPARATION_IN_FLIGHT_LIMIT = 32;
|
|
103590
103559
|
static ASSESSMENT_CACHE_TTL_MS = 60000;
|
|
103591
103560
|
static EXPORT_CONCURRENCY = 4;
|
|
103592
|
-
static ASSESSMENT_VERSION_CONFIRMATION_DELAY_MS = 25;
|
|
103593
103561
|
static SCORING_CONCURRENCY = 4;
|
|
103594
103562
|
static STALE_AWARD_AGE_MS = 15 * 60 * 1000;
|
|
103595
103563
|
deps;
|
|
@@ -103679,8 +103647,6 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
103679
103647
|
return this.startReview({ gameId, studentId, input, user });
|
|
103680
103648
|
}
|
|
103681
103649
|
const masteryStandard = input.purpose === "mastery" ? normalizeMasteryStandard(input.standard) : undefined;
|
|
103682
|
-
const requestFingerprint = this.startRequestFingerprint(input);
|
|
103683
|
-
const pendingSupersessions = new Set;
|
|
103684
103650
|
const candidates = await this.candidateIntegrations(gameId, studentId, input);
|
|
103685
103651
|
const lockKeys = candidates.rows.map((row) => assessmentRuntimeStartLockKey({
|
|
103686
103652
|
studentId,
|
|
@@ -103689,7 +103655,7 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
103689
103655
|
courseId: row.courseId
|
|
103690
103656
|
}));
|
|
103691
103657
|
return this.deps.assessmentRuntimeLock(this.deps.db, lockKeys, async (db2) => {
|
|
103692
|
-
const context2 = await this.selectIntegration(candidates,
|
|
103658
|
+
const context2 = await this.selectIntegration(candidates, input);
|
|
103693
103659
|
const attemptScope = {
|
|
103694
103660
|
studentId,
|
|
103695
103661
|
activityId: input.activityId,
|
|
@@ -103702,39 +103668,28 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
103702
103668
|
this.liveTests(context2.integration.id, input.purpose, db2, masteryStandard),
|
|
103703
103669
|
this.listAttempts(attemptScope)
|
|
103704
103670
|
]);
|
|
103705
|
-
const
|
|
103706
|
-
|
|
103707
|
-
|
|
103708
|
-
|
|
103709
|
-
|
|
103710
|
-
}
|
|
103711
|
-
|
|
103712
|
-
|
|
103713
|
-
|
|
103714
|
-
|
|
103715
|
-
|
|
103716
|
-
|
|
103717
|
-
|
|
103718
|
-
|
|
103719
|
-
|
|
103720
|
-
|
|
103721
|
-
if (assessment.identifier !== decision.test.qtiTestIdentifier) {
|
|
103722
|
-
assessment = await this.loadAssessment(decision.test.qtiTestIdentifier);
|
|
103723
|
-
}
|
|
103724
|
-
const classId = await this.resolveClassId(context2.integration.courseId, context2.enrollment, studentId);
|
|
103671
|
+
const pending = await this.pendingAwardSnapshot(listing, context2);
|
|
103672
|
+
if (pending) {
|
|
103673
|
+
return pending;
|
|
103674
|
+
}
|
|
103675
|
+
const decision = this.selectFreshStart(tests, listing, input.purpose);
|
|
103676
|
+
const [assessment, classId, { attemptNumber, attemptId }] = await Promise.all([
|
|
103677
|
+
this.loadAssessment(decision.test.qtiTestIdentifier),
|
|
103678
|
+
this.resolveClassId(context2.integration.courseId, context2.enrollment, studentId),
|
|
103679
|
+
this.claimAttemptId({
|
|
103680
|
+
gameId,
|
|
103681
|
+
studentId,
|
|
103682
|
+
courseId: context2.integration.courseId,
|
|
103683
|
+
activityId: input.activityId,
|
|
103684
|
+
purpose: input.purpose
|
|
103685
|
+
}, nextAssessmentAttemptNumber(listing.attempts.values()))
|
|
103686
|
+
]);
|
|
103725
103687
|
const lineItemId = await this.ensureLineItem({
|
|
103726
103688
|
classId,
|
|
103727
103689
|
integration: context2.integration,
|
|
103728
103690
|
purpose: input.purpose,
|
|
103729
103691
|
assessment
|
|
103730
103692
|
});
|
|
103731
|
-
const { attemptNumber, attemptId } = await this.claimAttemptId({
|
|
103732
|
-
gameId,
|
|
103733
|
-
studentId,
|
|
103734
|
-
courseId: context2.integration.courseId,
|
|
103735
|
-
activityId: input.activityId,
|
|
103736
|
-
purpose: input.purpose
|
|
103737
|
-
}, Math.max(0, ...[...refreshed.values()].map((entry) => entry.metadata.attemptNumber)) + 1);
|
|
103738
103693
|
const timestamp6 = new Date().toISOString();
|
|
103739
103694
|
const metadataBase = {
|
|
103740
103695
|
version: PLAYCADEMY_ASSESSMENT_RESULT_METADATA_VERSION,
|
|
@@ -103759,7 +103714,7 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
103759
103714
|
...metadataBase,
|
|
103760
103715
|
purpose: input.purpose,
|
|
103761
103716
|
mastery: {
|
|
103762
|
-
requestFingerprint,
|
|
103717
|
+
requestFingerprint: masteryRequestFingerprint(input),
|
|
103763
103718
|
standard: masteryStandard
|
|
103764
103719
|
}
|
|
103765
103720
|
} : { ...metadataBase, purpose: input.purpose };
|
|
@@ -103777,17 +103732,24 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
103777
103732
|
grade: context2.integration.grade
|
|
103778
103733
|
})
|
|
103779
103734
|
});
|
|
103780
|
-
await
|
|
103781
|
-
|
|
103735
|
+
const [snapshot] = await Promise.all([
|
|
103736
|
+
this.snapshot(result, metadata2, assessment, context2.integration),
|
|
103737
|
+
this.supersedeReplacedAttempts(db2, listing.attempts, context2.enrollment.id)
|
|
103738
|
+
]);
|
|
103739
|
+
return snapshot;
|
|
103782
103740
|
});
|
|
103783
103741
|
}
|
|
103784
103742
|
async prepareNewAttempt(params, requestHash) {
|
|
103785
103743
|
const startedAt = Date.now();
|
|
103786
103744
|
const [candidates, results] = await Promise.all([
|
|
103787
103745
|
this.candidateIntegrations(params.gameId, params.studentId, params.input),
|
|
103788
|
-
this.
|
|
103746
|
+
this.activityAssessmentResults({
|
|
103747
|
+
studentId: params.studentId,
|
|
103748
|
+
activityId: params.input.activityId,
|
|
103749
|
+
purpose: params.input.purpose
|
|
103750
|
+
})
|
|
103789
103751
|
]);
|
|
103790
|
-
const context2 = await this.selectIntegration(candidates, params.
|
|
103752
|
+
const context2 = await this.selectIntegration(candidates, params.input);
|
|
103791
103753
|
const attemptScope = {
|
|
103792
103754
|
studentId: params.studentId,
|
|
103793
103755
|
activityId: params.input.activityId,
|
|
@@ -103798,40 +103760,30 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
103798
103760
|
};
|
|
103799
103761
|
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);
|
|
103800
103762
|
const listing = await this.attemptListing(results, attemptScope);
|
|
103801
|
-
|
|
103802
|
-
|
|
103803
|
-
|
|
103804
|
-
const selection = resolveAssessmentPreparationSelection(decision, candidates.rows.length);
|
|
103805
|
-
if (selection.status === "blocked") {
|
|
103806
|
-
this.recordPreparation(selection.result.status, params.input.purpose, startedAt, {
|
|
103807
|
-
reason: selection.result.status === "canonical_required" ? selection.result.reason : "multiple_candidate_courses"
|
|
103763
|
+
if (this.pendingAwardAttempt(listing, context2.enrollment.id)) {
|
|
103764
|
+
this.recordPreparation("unprepared", params.input.purpose, startedAt, {
|
|
103765
|
+
reason: "pending_award"
|
|
103808
103766
|
});
|
|
103809
|
-
return
|
|
103767
|
+
return { status: "unprepared", reason: "prerequisite_unavailable" };
|
|
103810
103768
|
}
|
|
103811
|
-
if (
|
|
103812
|
-
|
|
103769
|
+
if (candidates.rows.length !== 1) {
|
|
103770
|
+
this.recordPreparation("unprepared", params.input.purpose, startedAt, {
|
|
103771
|
+
reason: "multiple_candidate_courses"
|
|
103772
|
+
});
|
|
103773
|
+
return { status: "unprepared", reason: "prerequisite_unavailable" };
|
|
103813
103774
|
}
|
|
103814
103775
|
const preparation = {
|
|
103815
103776
|
context: context2,
|
|
103816
103777
|
tests,
|
|
103817
103778
|
listing,
|
|
103818
|
-
decision:
|
|
103779
|
+
decision: this.selectFreshStart(tests, listing, params.input.purpose)
|
|
103819
103780
|
};
|
|
103820
103781
|
let prepared;
|
|
103821
103782
|
try {
|
|
103822
103783
|
prepared = await this.buildPreparationPlan(params, requestHash, preparation);
|
|
103823
|
-
|
|
103824
|
-
if (!assessment) {
|
|
103784
|
+
if (!await this.loadPreparedPlanAssessment(prepared.plan)) {
|
|
103825
103785
|
throw new Error("Prepared assessment artifact is unavailable");
|
|
103826
103786
|
}
|
|
103827
|
-
await this.provisionPreparationPrerequisites(() => this.ensureTranscriptLineItems({
|
|
103828
|
-
version: 1,
|
|
103829
|
-
gameId: prepared.plan.gameId,
|
|
103830
|
-
studentId: prepared.plan.studentId,
|
|
103831
|
-
attemptId: prepared.plan.attemptId,
|
|
103832
|
-
parentLineItemId: prepared.plan.parentLineItemId,
|
|
103833
|
-
metadata: buildPreparedAssessmentMetadata(prepared.plan, new Date().toISOString(), assessment.title)
|
|
103834
|
-
}, assessment));
|
|
103835
103787
|
} catch (error88) {
|
|
103836
103788
|
if (error88 instanceof ValidationError || error88 instanceof AssessmentRuntimeError) {
|
|
103837
103789
|
throw error88;
|
|
@@ -103920,6 +103872,7 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
103920
103872
|
courseName: context2.enrollment.course.title,
|
|
103921
103873
|
...attempt,
|
|
103922
103874
|
parentLineItemId,
|
|
103875
|
+
supersedes: [...this.replacedAttemptIds(listing.attempts, context2.enrollment.id)],
|
|
103923
103876
|
selectedTest: {
|
|
103924
103877
|
assessmentKey: decision.test.assessmentKey,
|
|
103925
103878
|
identifier: assessment.identifier,
|
|
@@ -103971,26 +103924,12 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
103971
103924
|
throw AssessmentRuntimeError.from(assessmentReviewBankShortage(selected.fulfillment));
|
|
103972
103925
|
}
|
|
103973
103926
|
const artifact = await this.loadOrCompileReviewBankArtifact(catalog);
|
|
103974
|
-
const assessment = projectReviewAssessment(artifact.assessment, catalog.bank, selected.selections);
|
|
103975
103927
|
const source = { assessment: artifact.assessment, bank: catalog.bank };
|
|
103976
103928
|
const parentLineItemId = await this.provisionPreparationPrerequisites(() => this.ensureReviewBankLineItem({
|
|
103977
103929
|
integration: context2.integration,
|
|
103978
103930
|
activityId: params.input.activityId,
|
|
103979
103931
|
source
|
|
103980
103932
|
}));
|
|
103981
|
-
await this.provisionPreparationPrerequisites(() => runWithConcurrency(assessment.items, TimebackAssessmentRuntimeService.SCORING_CONCURRENCY, (item) => {
|
|
103982
|
-
const indexed = catalog.bank.items.find((candidate) => candidate.itemIdentifier === item.identifier);
|
|
103983
|
-
if (!indexed) {
|
|
103984
|
-
throw new Error(`Review-bank index is missing item ${item.identifier}`);
|
|
103985
|
-
}
|
|
103986
|
-
return this.ensureReviewQuestionLineItem({
|
|
103987
|
-
parentLineItemId,
|
|
103988
|
-
integration: context2.integration,
|
|
103989
|
-
bankRevision: catalog.bank.bankRevision,
|
|
103990
|
-
standards: [...indexed.standards],
|
|
103991
|
-
item
|
|
103992
|
-
});
|
|
103993
|
-
}));
|
|
103994
103933
|
const attempt = await this.provisionPreparationPrerequisites(() => this.claimAttemptId({
|
|
103995
103934
|
gameId: params.gameId,
|
|
103996
103935
|
studentId: params.studentId,
|
|
@@ -104012,6 +103951,7 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
104012
103951
|
courseName: context2.enrollment.course.title,
|
|
104013
103952
|
...attempt,
|
|
104014
103953
|
parentLineItemId,
|
|
103954
|
+
supersedes: [...this.replacedAttemptIds(listing.attempts, context2.enrollment.id)],
|
|
104015
103955
|
selectedTest: {
|
|
104016
103956
|
assessmentKey: decision.test.assessmentKey,
|
|
104017
103957
|
identifier: artifact.assessment.identifier,
|
|
@@ -104060,10 +104000,7 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
104060
104000
|
if (!integration || !await this.preparedPlanTestIsLive(verified, db2)) {
|
|
104061
104001
|
return null;
|
|
104062
104002
|
}
|
|
104063
|
-
|
|
104064
|
-
return null;
|
|
104065
|
-
}
|
|
104066
|
-
return this.createPreparedAttempt(verified, assessment, integration);
|
|
104003
|
+
return this.claimPreparedAttempt(db2, verified, assessment, integration);
|
|
104067
104004
|
});
|
|
104068
104005
|
if (!outcome) {
|
|
104069
104006
|
this.recordPreparedStart("fallback", params.input.purpose, startedAt, {
|
|
@@ -104088,8 +104025,6 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
104088
104025
|
studentId,
|
|
104089
104026
|
input
|
|
104090
104027
|
}) {
|
|
104091
|
-
const requestFingerprint = this.startRequestFingerprint(input);
|
|
104092
|
-
const pendingSupersessions = new Set;
|
|
104093
104028
|
const candidates = await this.candidateIntegrations(gameId, studentId, input);
|
|
104094
104029
|
const lockKeys = candidates.rows.map((row) => assessmentRuntimeStartLockKey({
|
|
104095
104030
|
studentId,
|
|
@@ -104098,7 +104033,7 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
104098
104033
|
courseId: row.courseId
|
|
104099
104034
|
}));
|
|
104100
104035
|
return this.deps.assessmentRuntimeLock(this.deps.db, lockKeys, async (db2) => {
|
|
104101
|
-
const context2 = await this.selectIntegration(candidates,
|
|
104036
|
+
const context2 = await this.selectIntegration(candidates, input);
|
|
104102
104037
|
const attemptScope = {
|
|
104103
104038
|
studentId,
|
|
104104
104039
|
activityId: input.activityId,
|
|
@@ -104111,32 +104046,25 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
104111
104046
|
this.liveTests(context2.integration.id, input.purpose, db2, undefined, input.diagnosticKey),
|
|
104112
104047
|
this.listAttempts(attemptScope)
|
|
104113
104048
|
]);
|
|
104114
|
-
const
|
|
104115
|
-
if (
|
|
104116
|
-
|
|
104117
|
-
return initialResolution.snapshot;
|
|
104049
|
+
const pending = await this.pendingAwardSnapshot(listing, context2);
|
|
104050
|
+
if (pending) {
|
|
104051
|
+
return pending;
|
|
104118
104052
|
}
|
|
104119
|
-
|
|
104053
|
+
const decision = this.selectFreshStart(tests, listing, input.purpose);
|
|
104120
104054
|
let definition = await this.requireDiagnosticDefinition(decision.test.id, context2.integration.id, db2, true);
|
|
104121
|
-
|
|
104122
|
-
|
|
104123
|
-
|
|
104124
|
-
|
|
104125
|
-
|
|
104126
|
-
|
|
104127
|
-
|
|
104128
|
-
|
|
104129
|
-
|
|
104130
|
-
|
|
104131
|
-
|
|
104132
|
-
|
|
104133
|
-
|
|
104134
|
-
routing = await this.initializeHostedDiagnostic(definition, assessment);
|
|
104135
|
-
} else {
|
|
104136
|
-
definition = await this.requireDiagnosticDefinition(definition.id, context2.integration.id, db2, true);
|
|
104137
|
-
routing = await this.initializeHostedDiagnostic(definition, assessment);
|
|
104138
|
-
}
|
|
104139
|
-
const classId = await this.resolveClassId(context2.integration.courseId, context2.enrollment, studentId);
|
|
104055
|
+
const [assessment, classId, { attemptNumber, attemptId }] = await Promise.all([
|
|
104056
|
+
this.loadAssessment(definition.qtiTestIdentifier),
|
|
104057
|
+
this.resolveClassId(context2.integration.courseId, context2.enrollment, studentId),
|
|
104058
|
+
this.claimAttemptId({
|
|
104059
|
+
gameId,
|
|
104060
|
+
studentId,
|
|
104061
|
+
courseId: context2.integration.courseId,
|
|
104062
|
+
activityId: input.activityId,
|
|
104063
|
+
purpose: input.purpose
|
|
104064
|
+
}, nextAssessmentAttemptNumber(listing.attempts.values()))
|
|
104065
|
+
]);
|
|
104066
|
+
definition = await this.requireDiagnosticDefinition(definition.id, context2.integration.id, db2, true);
|
|
104067
|
+
const routing = await this.initializeHostedDiagnostic(definition, assessment);
|
|
104140
104068
|
const lineItemId = await this.ensureLineItem({
|
|
104141
104069
|
classId,
|
|
104142
104070
|
integration: context2.integration,
|
|
@@ -104148,13 +104076,6 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
104148
104076
|
routingRevision: routing.routingRevision
|
|
104149
104077
|
}
|
|
104150
104078
|
});
|
|
104151
|
-
const { attemptNumber, attemptId } = await this.claimAttemptId({
|
|
104152
|
-
gameId,
|
|
104153
|
-
studentId,
|
|
104154
|
-
courseId: context2.integration.courseId,
|
|
104155
|
-
activityId: input.activityId,
|
|
104156
|
-
purpose: input.purpose
|
|
104157
|
-
}, Math.max(0, ...[...refreshed.values()].map((entry) => entry.metadata.attemptNumber)) + 1);
|
|
104158
104079
|
const timestamp6 = new Date().toISOString();
|
|
104159
104080
|
const metadata2 = {
|
|
104160
104081
|
version: PLAYCADEMY_ASSESSMENT_RESULT_METADATA_VERSION,
|
|
@@ -104198,10 +104119,13 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
104198
104119
|
grade: context2.integration.grade
|
|
104199
104120
|
})
|
|
104200
104121
|
});
|
|
104201
|
-
await
|
|
104202
|
-
|
|
104203
|
-
|
|
104204
|
-
|
|
104122
|
+
const [snapshot] = await Promise.all([
|
|
104123
|
+
this.diagnosticSnapshot(result, metadata2, assessment, context2.integration, {
|
|
104124
|
+
state: routing.state
|
|
104125
|
+
}),
|
|
104126
|
+
this.supersedeReplacedAttempts(db2, listing.attempts, context2.enrollment.id)
|
|
104127
|
+
]);
|
|
104128
|
+
return snapshot;
|
|
104205
104129
|
});
|
|
104206
104130
|
}
|
|
104207
104131
|
async startReview({
|
|
@@ -104211,7 +104135,6 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
104211
104135
|
}) {
|
|
104212
104136
|
const preparationStartedAt = Date.now();
|
|
104213
104137
|
const requestFingerprint = reviewRequestFingerprint(input, DEFAULT_REVIEW_SELECTION_POLICY);
|
|
104214
|
-
const pendingSupersessions = new Set;
|
|
104215
104138
|
const normalizedRequest = normalizeReviewRequest(input);
|
|
104216
104139
|
const candidates = await this.candidateIntegrations(gameId, studentId, input);
|
|
104217
104140
|
const lockKeys = candidates.rows.map((row) => assessmentRuntimeStartLockKey({
|
|
@@ -104221,7 +104144,7 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
104221
104144
|
courseId: row.courseId
|
|
104222
104145
|
}));
|
|
104223
104146
|
return this.deps.assessmentRuntimeLock(this.deps.db, lockKeys, async (db2) => {
|
|
104224
|
-
const context2 = await this.selectIntegration(candidates,
|
|
104147
|
+
const context2 = await this.selectIntegration(candidates, input);
|
|
104225
104148
|
const attemptScope = {
|
|
104226
104149
|
studentId,
|
|
104227
104150
|
activityId: input.activityId,
|
|
@@ -104234,42 +104157,22 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
104234
104157
|
this.liveTests(context2.integration.id, input.purpose, db2),
|
|
104235
104158
|
this.listAttempts(attemptScope)
|
|
104236
104159
|
]);
|
|
104237
|
-
const
|
|
104238
|
-
|
|
104239
|
-
|
|
104240
|
-
await this.persistPendingSupersessionsBestEffort(db2, pendingSupersessions);
|
|
104241
|
-
return initialResolution.snapshot;
|
|
104242
|
-
}
|
|
104243
|
-
let decision = initialResolution.decision;
|
|
104244
|
-
if (tests.length === 0 || !decision) {
|
|
104245
|
-
throw AssessmentRuntimeError.from(assessmentNoEligibleTests(input.purpose));
|
|
104160
|
+
const pending = await this.pendingAwardSnapshot(listing, context2);
|
|
104161
|
+
if (pending) {
|
|
104162
|
+
return pending;
|
|
104246
104163
|
}
|
|
104164
|
+
const decision = this.selectFreshStart(tests, listing, input.purpose);
|
|
104247
104165
|
if (tests.length > 1) {
|
|
104248
104166
|
throw new ValidationError("Standards review requires exactly one live review-bank assessment.");
|
|
104249
104167
|
}
|
|
104250
104168
|
const manifestStartedAt = Date.now();
|
|
104251
|
-
|
|
104169
|
+
const catalog = await this.loadReviewBankCatalog(decision.test.qtiTestIdentifier);
|
|
104252
104170
|
this.recordReviewPreparationPhase("manifest_load", manifestStartedAt, {
|
|
104253
104171
|
candidateCount: catalog.bank.items.length
|
|
104254
104172
|
});
|
|
104255
|
-
const refreshedListing = await this.listAttempts(attemptScope);
|
|
104256
|
-
const refreshed = refreshedListing.attempts;
|
|
104257
|
-
const refreshedResolution = await this.resumeAvailableAttempt(tests, refreshed, requestFingerprint, pendingSupersessions, (resumeDecision) => this.resumeReviewSnapshot(resumeDecision, refreshed, context2));
|
|
104258
|
-
if (refreshedResolution.kind === "resumed") {
|
|
104259
|
-
await this.persistPendingSupersessionsBestEffort(db2, pendingSupersessions);
|
|
104260
|
-
return refreshedResolution.snapshot;
|
|
104261
|
-
}
|
|
104262
|
-
const refreshedDecision = refreshedResolution.decision;
|
|
104263
|
-
if (!refreshedDecision || refreshedDecision.kind !== "start") {
|
|
104264
|
-
throw AssessmentRuntimeError.from(assessmentNoEligibleTests(input.purpose));
|
|
104265
|
-
}
|
|
104266
|
-
decision = refreshedDecision;
|
|
104267
|
-
if (catalog.test.identifier !== decision.test.qtiTestIdentifier) {
|
|
104268
|
-
catalog = await this.loadReviewBankCatalog(decision.test.qtiTestIdentifier);
|
|
104269
|
-
}
|
|
104270
104173
|
const exposures = assessmentReviewTranscriptExposures({
|
|
104271
|
-
attempts:
|
|
104272
|
-
children:
|
|
104174
|
+
attempts: listing.attempts.values(),
|
|
104175
|
+
children: listing.reviewChildren.values(),
|
|
104273
104176
|
bankRevision: catalog.bank.bankRevision
|
|
104274
104177
|
});
|
|
104275
104178
|
const selected = selectReviewItems({
|
|
@@ -104290,18 +104193,20 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
104290
104193
|
});
|
|
104291
104194
|
const assessment = projectReviewAssessment(source.assessment, source.bank, selected.selections);
|
|
104292
104195
|
const provisioningStartedAt = Date.now();
|
|
104293
|
-
const parentLineItemId = await
|
|
104294
|
-
|
|
104295
|
-
|
|
104296
|
-
|
|
104297
|
-
|
|
104298
|
-
|
|
104299
|
-
|
|
104300
|
-
|
|
104301
|
-
|
|
104302
|
-
|
|
104303
|
-
|
|
104304
|
-
|
|
104196
|
+
const [parentLineItemId, { attemptNumber, attemptId }] = await Promise.all([
|
|
104197
|
+
this.ensureReviewBankLineItem({
|
|
104198
|
+
integration: context2.integration,
|
|
104199
|
+
activityId: input.activityId,
|
|
104200
|
+
source
|
|
104201
|
+
}),
|
|
104202
|
+
this.claimAttemptId({
|
|
104203
|
+
gameId,
|
|
104204
|
+
studentId,
|
|
104205
|
+
courseId: context2.integration.courseId,
|
|
104206
|
+
activityId: input.activityId,
|
|
104207
|
+
purpose: input.purpose
|
|
104208
|
+
}, nextAssessmentAttemptNumber(listing.attempts.values()))
|
|
104209
|
+
]);
|
|
104305
104210
|
const timestamp6 = new Date().toISOString();
|
|
104306
104211
|
const metadata2 = {
|
|
104307
104212
|
version: PLAYCADEMY_ASSESSMENT_RESULT_METADATA_VERSION,
|
|
@@ -104345,13 +104250,16 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
104345
104250
|
grade: context2.integration.grade
|
|
104346
104251
|
})
|
|
104347
104252
|
});
|
|
104348
|
-
await
|
|
104253
|
+
const [snapshot] = await Promise.all([
|
|
104254
|
+
this.snapshot(result, metadata2, assessment, context2.integration),
|
|
104255
|
+
this.supersedeReplacedAttempts(db2, listing.attempts, context2.enrollment.id)
|
|
104256
|
+
]);
|
|
104349
104257
|
this.recordReviewPreparationPhase("provisioning", provisioningStartedAt);
|
|
104350
104258
|
this.recordReviewPreparationPhase("total", preparationStartedAt, {
|
|
104351
104259
|
candidateCount: catalog.bank.items.length,
|
|
104352
104260
|
selectedCount: selected.selections.length
|
|
104353
104261
|
});
|
|
104354
|
-
return
|
|
104262
|
+
return snapshot;
|
|
104355
104263
|
});
|
|
104356
104264
|
}
|
|
104357
104265
|
async get(params) {
|
|
@@ -104869,28 +104777,11 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
104869
104777
|
liveTestIntegrationIds: integrationIdsWithLiveTests
|
|
104870
104778
|
};
|
|
104871
104779
|
}
|
|
104872
|
-
async selectIntegration(candidates,
|
|
104873
|
-
const {
|
|
104874
|
-
rows: candidateRows,
|
|
104875
|
-
enrollmentByCourse,
|
|
104876
|
-
liveTestIntegrationIds: integrationIdsWithLiveTests
|
|
104877
|
-
} = candidates;
|
|
104878
|
-
const needsOpenAnnotation = candidateRows.length > 1 || candidateRows.some((row) => !integrationIdsWithLiveTests.has(row.id));
|
|
104879
|
-
const openAttemptScope = {
|
|
104880
|
-
studentId,
|
|
104881
|
-
activityId: input.activityId,
|
|
104882
|
-
purpose: input.purpose,
|
|
104883
|
-
enrollmentIdByCourse: new Map(candidateRows.map((row) => [row.courseId, enrollmentByCourse.get(row.courseId).id])),
|
|
104884
|
-
requestFingerprint: this.startRequestFingerprint(input)
|
|
104885
|
-
};
|
|
104886
|
-
let openAttemptCourseIds = new Set;
|
|
104887
|
-
if (needsOpenAnnotation) {
|
|
104888
|
-
openAttemptCourseIds = results ? this.openAttemptCourseIdsFromResults(results, openAttemptScope) : await this.openAttemptCourseIds(openAttemptScope);
|
|
104889
|
-
}
|
|
104780
|
+
async selectIntegration(candidates, input) {
|
|
104781
|
+
const { rows: candidateRows, enrollmentByCourse, liveTestIntegrationIds } = candidates;
|
|
104890
104782
|
const annotated = candidateRows.map((row) => ({
|
|
104891
104783
|
...row,
|
|
104892
|
-
hasLiveTests:
|
|
104893
|
-
hasOpenAttempt: openAttemptCourseIds.has(row.courseId)
|
|
104784
|
+
hasLiveTests: liveTestIntegrationIds.has(row.id)
|
|
104894
104785
|
}));
|
|
104895
104786
|
const selection = selectAssessmentCourseCandidate(annotated, Boolean(input.subject));
|
|
104896
104787
|
if (selection.outcome === "none") {
|
|
@@ -104919,15 +104810,19 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
104919
104810
|
standard: assessmentStandardForRow(row)
|
|
104920
104811
|
})).filter((test) => !matchesMasteryStandard || matchesMasteryStandard(test.standard));
|
|
104921
104812
|
}
|
|
104922
|
-
async
|
|
104923
|
-
|
|
104813
|
+
async activityAssessmentResults(scope) {
|
|
104814
|
+
const [parents, children] = await Promise.all([
|
|
104815
|
+
this.collectAssessmentResults(buildAssessmentAttemptResultWhere(scope)),
|
|
104816
|
+
scope.purpose === "review" ? this.activityChildResults(scope) : []
|
|
104817
|
+
]);
|
|
104818
|
+
return [...parents, ...children];
|
|
104819
|
+
}
|
|
104820
|
+
activityChildResults(scope) {
|
|
104821
|
+
return this.collectAssessmentResults(buildAssessmentChildResultWhere(scope));
|
|
104924
104822
|
}
|
|
104925
|
-
async
|
|
104823
|
+
async collectAssessmentResults(where) {
|
|
104926
104824
|
const stream = this.requireClient().api.oneroster.assessmentResults.stream({
|
|
104927
|
-
where
|
|
104928
|
-
"student.sourcedId": studentId,
|
|
104929
|
-
status: ONEROSTER_STATUS2.active
|
|
104930
|
-
}
|
|
104825
|
+
where
|
|
104931
104826
|
});
|
|
104932
104827
|
const results = [];
|
|
104933
104828
|
for await (const result of stream) {
|
|
@@ -104935,18 +104830,8 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
104935
104830
|
}
|
|
104936
104831
|
return results;
|
|
104937
104832
|
}
|
|
104938
|
-
openAttemptCourseIdsFromResults(results, scope) {
|
|
104939
|
-
const courseIds = new Set;
|
|
104940
|
-
for (const result of results) {
|
|
104941
|
-
const metadata2 = playcademyAssessmentResultMetadata(result.metadata);
|
|
104942
|
-
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))) {
|
|
104943
|
-
courseIds.add(metadata2.courseId);
|
|
104944
|
-
}
|
|
104945
|
-
}
|
|
104946
|
-
return courseIds;
|
|
104947
|
-
}
|
|
104948
104833
|
async listAttempts(scope) {
|
|
104949
|
-
return this.attemptListing(await this.
|
|
104834
|
+
return this.attemptListing(await this.activityAssessmentResults(scope), scope);
|
|
104950
104835
|
}
|
|
104951
104836
|
async attemptListing(results, scope) {
|
|
104952
104837
|
const attempts = new Map;
|
|
@@ -104975,7 +104860,7 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
104975
104860
|
}
|
|
104976
104861
|
}
|
|
104977
104862
|
}
|
|
104978
|
-
if (metadata2 &&
|
|
104863
|
+
if (metadata2 && this.attemptInScope(result, metadata2, scope)) {
|
|
104979
104864
|
const selectedAssessmentKey = metadata2.selectedTest.assessmentKey ?? assessmentKeyFromManagedQtiIdentifier(metadata2.selectedTest.identifier);
|
|
104980
104865
|
if (selectedAssessmentKey) {
|
|
104981
104866
|
attempts.set(result.sourcedId, {
|
|
@@ -104984,12 +104869,9 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
104984
104869
|
selection: {
|
|
104985
104870
|
attemptId: result.sourcedId,
|
|
104986
104871
|
selectedAssessmentKey,
|
|
104987
|
-
selectedTestIdentifier: metadata2.selectedTest.identifier,
|
|
104988
104872
|
inProgress: result.inProgress ?? "",
|
|
104989
104873
|
scoreStatus: result.scoreStatus,
|
|
104990
|
-
scoreDate: result.scoreDate
|
|
104991
|
-
updatedAt: result.dateLastModified ?? metadata2.updatedAt,
|
|
104992
|
-
resumable: metadata2.enrollmentId === scope.enrollmentId
|
|
104874
|
+
scoreDate: result.scoreDate
|
|
104993
104875
|
}
|
|
104994
104876
|
});
|
|
104995
104877
|
}
|
|
@@ -104997,114 +104879,61 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
104997
104879
|
}
|
|
104998
104880
|
return { attempts, reviewChildren };
|
|
104999
104881
|
}
|
|
105000
|
-
|
|
105001
|
-
|
|
105002
|
-
return attempts;
|
|
105003
|
-
}
|
|
105004
|
-
return new Map([...attempts].filter(([, attempt]) => this.attemptMatchesRequestFingerprint(attempt.metadata, requestFingerprint)));
|
|
105005
|
-
}
|
|
105006
|
-
startRequestFingerprint(input) {
|
|
105007
|
-
if (input.purpose === "review") {
|
|
105008
|
-
return reviewRequestFingerprint(input, DEFAULT_REVIEW_SELECTION_POLICY);
|
|
105009
|
-
}
|
|
105010
|
-
if (input.purpose === "mastery") {
|
|
105011
|
-
return masteryRequestFingerprint(input);
|
|
105012
|
-
}
|
|
105013
|
-
return input.purpose === "diagnostic" ? input.diagnosticKey : undefined;
|
|
105014
|
-
}
|
|
105015
|
-
attemptMatchesRequestFingerprint(metadata2, requestFingerprint) {
|
|
105016
|
-
if (metadata2.purpose === "review") {
|
|
105017
|
-
return metadata2.review.requestFingerprint === requestFingerprint;
|
|
105018
|
-
}
|
|
105019
|
-
if (metadata2.purpose === "mastery") {
|
|
105020
|
-
return metadata2.mastery.requestFingerprint === requestFingerprint;
|
|
105021
|
-
}
|
|
105022
|
-
if (metadata2.purpose === "diagnostic" && requestFingerprint !== undefined) {
|
|
105023
|
-
return this.isPlatformRoutedDiagnosticMetadata(metadata2) && metadata2.diagnostic.diagnosticKey === requestFingerprint;
|
|
105024
|
-
}
|
|
105025
|
-
return true;
|
|
105026
|
-
}
|
|
105027
|
-
requireDecision(decision, purpose) {
|
|
104882
|
+
selectFreshStart(tests, listing, purpose) {
|
|
104883
|
+
const decision = selectRuntimeAssessment(tests, [...listing.attempts.values()].map((entry) => entry.selection));
|
|
105028
104884
|
if (!decision) {
|
|
105029
104885
|
throw AssessmentRuntimeError.from(assessmentNoEligibleTests(purpose));
|
|
105030
104886
|
}
|
|
105031
104887
|
return decision;
|
|
105032
104888
|
}
|
|
105033
|
-
|
|
105034
|
-
|
|
105035
|
-
|
|
105036
|
-
"app.assessment.attempt_id": decision.attempt.attemptId,
|
|
105037
|
-
"app.assessment.additional_attempt_ids": decision.additionalResumableAttemptIds.join(",")
|
|
105038
|
-
});
|
|
105039
|
-
}
|
|
105040
|
-
const resumed = attempts.get(decision.attempt.attemptId);
|
|
105041
|
-
return this.snapshotForResult(resumed.result, resumed.metadata, integration);
|
|
105042
|
-
}
|
|
105043
|
-
async resumeDiagnosticSnapshot(decision, attempts, integration, _db = this.deps.db) {
|
|
105044
|
-
const resumed = attempts.get(decision.attempt.attemptId);
|
|
105045
|
-
return this.snapshotForResult(resumed.result, resumed.metadata, integration);
|
|
104889
|
+
pendingAwardAttempt(listing, enrollmentId) {
|
|
104890
|
+
const pending = [...listing.attempts.values()].filter((attempt) => attempt.metadata.enrollmentId === enrollmentId && this.isAwaitingAward(attempt.result));
|
|
104891
|
+
return pending.toSorted((left, right) => compareNewestCompletedAssessmentAttempt(left.selection, right.selection))[0] ?? null;
|
|
105046
104892
|
}
|
|
105047
|
-
async
|
|
105048
|
-
const
|
|
105049
|
-
|
|
104893
|
+
async pendingAwardSnapshot(listing, context2) {
|
|
104894
|
+
const pending = this.pendingAwardAttempt(listing, context2.enrollment.id);
|
|
104895
|
+
if (!pending) {
|
|
104896
|
+
return null;
|
|
104897
|
+
}
|
|
104898
|
+
addEvent("assessment.pending_award_returned", {
|
|
104899
|
+
"app.assessment.attempt_id": pending.result.sourcedId
|
|
104900
|
+
});
|
|
104901
|
+
return this.snapshotForResult(pending.result, pending.metadata, context2.integration);
|
|
105050
104902
|
}
|
|
105051
|
-
|
|
105052
|
-
|
|
105053
|
-
|
|
105054
|
-
|
|
105055
|
-
|
|
105056
|
-
throw error88;
|
|
105057
|
-
}
|
|
105058
|
-
if (shouldConfirmAssessmentVersionUnavailable(error88.details)) {
|
|
105059
|
-
await sleep(TimebackAssessmentRuntimeService.ASSESSMENT_VERSION_CONFIRMATION_DELAY_MS);
|
|
105060
|
-
try {
|
|
105061
|
-
return await resume();
|
|
105062
|
-
} catch (confirmationError) {
|
|
105063
|
-
if (!(confirmationError instanceof AssessmentRuntimeError) || confirmationError.runtimeCode !== "SELECTED_TEST_VERSION_UNAVAILABLE") {
|
|
105064
|
-
throw confirmationError;
|
|
105065
|
-
}
|
|
105066
|
-
}
|
|
104903
|
+
replacedAttemptIds(attempts, enrollmentId) {
|
|
104904
|
+
const replaced = new Set;
|
|
104905
|
+
for (const attempt of attempts.values()) {
|
|
104906
|
+
if (attempt.metadata.enrollmentId === enrollmentId && this.isOpen(attempt.result)) {
|
|
104907
|
+
replaced.add(attempt.result.sourcedId);
|
|
105067
104908
|
}
|
|
105068
|
-
const staged = attempts.get(decision.attempt.attemptId);
|
|
105069
|
-
pendingSupersessions.add(staged.result.sourcedId);
|
|
105070
|
-
stageAssessmentAttemptSupersession(staged);
|
|
105071
|
-
addEvent("assessment.attempt_supersession_staged", {
|
|
105072
|
-
"app.assessment.attempt_id": decision.attempt.attemptId,
|
|
105073
|
-
"app.assessment.reason": "selected_test_version_unavailable"
|
|
105074
|
-
});
|
|
105075
|
-
return null;
|
|
105076
104909
|
}
|
|
104910
|
+
return replaced;
|
|
104911
|
+
}
|
|
104912
|
+
supersedeReplacedAttempts(db2, attempts, enrollmentId) {
|
|
104913
|
+
return this.supersedeAttemptsBestEffort(db2, this.replacedAttemptIds(attempts, enrollmentId));
|
|
105077
104914
|
}
|
|
105078
|
-
async
|
|
104915
|
+
async supersedeAttempts(db2, attemptIds) {
|
|
105079
104916
|
for (const attemptId of [...attemptIds].toSorted()) {
|
|
105080
104917
|
await this.deps.assessmentRuntimeLock(db2, [assessmentRuntimeAttemptLockKey(attemptId)], async () => {
|
|
105081
|
-
|
|
105082
|
-
|
|
105083
|
-
result = await this.requireClient().api.oneroster.assessmentResults.get(attemptId);
|
|
105084
|
-
} catch (error88) {
|
|
105085
|
-
if (isApiError(error88) && error88.statusCode === 404) {
|
|
105086
|
-
return;
|
|
105087
|
-
}
|
|
105088
|
-
throw error88;
|
|
105089
|
-
}
|
|
105090
|
-
const update2 = buildAssessmentAttemptSupersessionUpdate(result);
|
|
104918
|
+
const result = await this.findAttemptResult(attemptId);
|
|
104919
|
+
const update2 = result && buildAssessmentAttemptSupersessionUpdate(result);
|
|
105091
104920
|
if (!update2) {
|
|
105092
104921
|
return;
|
|
105093
104922
|
}
|
|
105094
104923
|
await this.requireClient().api.oneroster.assessmentResults.upsert(attemptId, update2);
|
|
105095
104924
|
addEvent("assessment.attempt_superseded", {
|
|
105096
104925
|
"app.assessment.attempt_id": attemptId,
|
|
105097
|
-
"app.assessment.reason": "
|
|
104926
|
+
"app.assessment.reason": "replaced_by_fresh_start"
|
|
105098
104927
|
});
|
|
105099
104928
|
});
|
|
105100
104929
|
}
|
|
105101
104930
|
}
|
|
105102
|
-
async
|
|
104931
|
+
async supersedeAttemptsBestEffort(db2, attemptIds) {
|
|
105103
104932
|
if (attemptIds.size === 0) {
|
|
105104
104933
|
return;
|
|
105105
104934
|
}
|
|
105106
104935
|
try {
|
|
105107
|
-
await this.
|
|
104936
|
+
await this.supersedeAttempts(db2, attemptIds);
|
|
105108
104937
|
} catch (error88) {
|
|
105109
104938
|
addEvent("assessment.attempt_supersession_persist_failed", {
|
|
105110
104939
|
"app.assessment.attempt_ids": [...attemptIds].toSorted().join(","),
|
|
@@ -105113,10 +104942,6 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
105113
104942
|
});
|
|
105114
104943
|
}
|
|
105115
104944
|
}
|
|
105116
|
-
async resumeReviewSnapshot(decision, attempts, context2) {
|
|
105117
|
-
const resumed = attempts.get(decision.attempt.attemptId);
|
|
105118
|
-
return this.snapshotForResult(resumed.result, resumed.metadata, context2.integration);
|
|
105119
|
-
}
|
|
105120
104945
|
recordReviewPreparationPhase(phase, startedAt, counts = {}) {
|
|
105121
104946
|
addEvent("assessment.review_preparation_phase", {
|
|
105122
104947
|
"app.assessment.review_phase": phase,
|
|
@@ -105467,12 +105292,7 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
105467
105292
|
}
|
|
105468
105293
|
});
|
|
105469
105294
|
} catch (error88) {
|
|
105470
|
-
|
|
105471
|
-
throw error88;
|
|
105472
|
-
}
|
|
105473
|
-
addEvent("assessment.line_item_create_conflict", {
|
|
105474
|
-
"app.assessment.line_item_id": lineItemId
|
|
105475
|
-
});
|
|
105295
|
+
await this.confirmLineItemAfterRefusedCreate(lineItemId, error88, "assessment.line_item_create_conflict", () => client2.api.oneroster.assessmentLineItems.get(lineItemId));
|
|
105476
105296
|
}
|
|
105477
105297
|
return lineItemId;
|
|
105478
105298
|
}
|
|
@@ -105514,15 +105334,26 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
105514
105334
|
}
|
|
105515
105335
|
});
|
|
105516
105336
|
} catch (error88) {
|
|
105517
|
-
|
|
105518
|
-
throw error88;
|
|
105519
|
-
}
|
|
105520
|
-
addEvent("assessment.review_bank_line_item_create_conflict", {
|
|
105521
|
-
"app.assessment.line_item_id": lineItemId
|
|
105522
|
-
});
|
|
105337
|
+
await this.confirmLineItemAfterRefusedCreate(lineItemId, error88, "assessment.review_bank_line_item_create_conflict", () => client2.api.oneroster.assessmentLineItems.get(lineItemId));
|
|
105523
105338
|
}
|
|
105524
105339
|
return lineItemId;
|
|
105525
105340
|
}
|
|
105341
|
+
async confirmLineItemAfterRefusedCreate(lineItemId, createError, event, read) {
|
|
105342
|
+
let stored;
|
|
105343
|
+
try {
|
|
105344
|
+
stored = await read();
|
|
105345
|
+
} catch (readError) {
|
|
105346
|
+
if (isApiError(readError) && readError.statusCode === 404) {
|
|
105347
|
+
throw createError;
|
|
105348
|
+
}
|
|
105349
|
+
throw readError;
|
|
105350
|
+
}
|
|
105351
|
+
addEvent(event, {
|
|
105352
|
+
"app.assessment.line_item_id": lineItemId,
|
|
105353
|
+
...isApiError(createError) ? { "app.timeback.status": createError.statusCode } : {}
|
|
105354
|
+
});
|
|
105355
|
+
return stored;
|
|
105356
|
+
}
|
|
105526
105357
|
tokenContext(attempt) {
|
|
105527
105358
|
return {
|
|
105528
105359
|
version: 1,
|
|
@@ -105576,10 +105407,7 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
105576
105407
|
try {
|
|
105577
105408
|
await store.create({ sourcedId: id, title: item.title, ...expected });
|
|
105578
105409
|
} catch (error88) {
|
|
105579
|
-
|
|
105580
|
-
throw error88;
|
|
105581
|
-
}
|
|
105582
|
-
this.assertAssessmentChildLineItem(id, await store.get(id), expected);
|
|
105410
|
+
this.assertAssessmentChildLineItem(id, await this.confirmLineItemAfterRefusedCreate(id, error88, "assessment.question_line_item_create_conflict", () => store.get(id)), expected);
|
|
105583
105411
|
}
|
|
105584
105412
|
}
|
|
105585
105413
|
this.childLineItemCache.set(id, true);
|
|
@@ -105645,14 +105473,8 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
105645
105473
|
...expected
|
|
105646
105474
|
});
|
|
105647
105475
|
} catch (error88) {
|
|
105648
|
-
|
|
105649
|
-
throw error88;
|
|
105650
|
-
}
|
|
105651
|
-
const stored = await client2.api.oneroster.assessmentLineItems.get(lineItemId);
|
|
105476
|
+
const stored = await this.confirmLineItemAfterRefusedCreate(lineItemId, error88, "assessment.diagnostic_question_line_item_create_conflict", () => client2.api.oneroster.assessmentLineItems.get(lineItemId));
|
|
105652
105477
|
this.assertAssessmentChildLineItem(lineItemId, stored, expected);
|
|
105653
|
-
addEvent("assessment.diagnostic_question_line_item_create_conflict", {
|
|
105654
|
-
"app.assessment.line_item_id": lineItemId
|
|
105655
|
-
});
|
|
105656
105478
|
}
|
|
105657
105479
|
return lineItemId;
|
|
105658
105480
|
}
|
|
@@ -105691,14 +105513,8 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
105691
105513
|
...expected
|
|
105692
105514
|
});
|
|
105693
105515
|
} catch (error88) {
|
|
105694
|
-
|
|
105695
|
-
throw error88;
|
|
105696
|
-
}
|
|
105697
|
-
const stored = await client2.api.oneroster.assessmentLineItems.get(lineItemId);
|
|
105516
|
+
const stored = await this.confirmLineItemAfterRefusedCreate(lineItemId, error88, "assessment.review_question_line_item_create_conflict", () => client2.api.oneroster.assessmentLineItems.get(lineItemId));
|
|
105698
105517
|
this.assertAssessmentChildLineItem(lineItemId, stored, expected);
|
|
105699
|
-
addEvent("assessment.review_question_line_item_create_conflict", {
|
|
105700
|
-
"app.assessment.line_item_id": lineItemId
|
|
105701
|
-
});
|
|
105702
105518
|
}
|
|
105703
105519
|
return lineItemId;
|
|
105704
105520
|
}
|
|
@@ -105780,7 +105596,10 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
105780
105596
|
const graded = await resumeAssessmentTranscript({
|
|
105781
105597
|
suppliedTranscript,
|
|
105782
105598
|
checkpointTranscript: metadata2.transcript,
|
|
105783
|
-
recover: async () => recoverAssessmentTranscript(context2, await this.
|
|
105599
|
+
recover: async () => recoverAssessmentTranscript(context2, await this.activityChildResults({
|
|
105600
|
+
studentId: context2.studentId,
|
|
105601
|
+
activityId: context2.metadata.activityId
|
|
105602
|
+
})),
|
|
105784
105603
|
grade: (transcript) => this.gradeTranscript(context2, transcript)
|
|
105785
105604
|
});
|
|
105786
105605
|
const session2 = await this.assessmentSession(context2, graded.transcript);
|
|
@@ -105939,7 +105758,22 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
105939
105758
|
}
|
|
105940
105759
|
return projectReviewAssessment(playable, preparedReviewBankIndex(plan), plan.review.selections);
|
|
105941
105760
|
}
|
|
105942
|
-
async
|
|
105761
|
+
async claimPreparedAttempt(db2, plan, assessment, integration) {
|
|
105762
|
+
const occupant = await this.findAttemptResult(plan.attemptId);
|
|
105763
|
+
if (occupant) {
|
|
105764
|
+
const metadata3 = playcademyAssessmentResultMetadata(occupant.metadata);
|
|
105765
|
+
if (occupant.status !== ONEROSTER_STATUS2.active || !metadata3 || !this.attemptInScope(occupant, metadata3, plan) || metadata3.enrollmentId !== plan.enrollmentId || !this.isOpen(occupant) || !this.attemptMatchesPlan(metadata3, plan)) {
|
|
105766
|
+
return null;
|
|
105767
|
+
}
|
|
105768
|
+
addEvent("assessment.prepared_attempt_recovered", {
|
|
105769
|
+
"app.assessment.attempt_id": plan.attemptId
|
|
105770
|
+
});
|
|
105771
|
+
const [snapshot2] = await Promise.all([
|
|
105772
|
+
this.snapshotForResult(occupant, metadata3, integration),
|
|
105773
|
+
this.supersedeAttemptsBestEffort(db2, new Set(plan.supersedes))
|
|
105774
|
+
]);
|
|
105775
|
+
return snapshot2;
|
|
105776
|
+
}
|
|
105943
105777
|
const timestamp6 = new Date().toISOString();
|
|
105944
105778
|
const metadata2 = buildPreparedAssessmentMetadata(plan, timestamp6, assessment.title);
|
|
105945
105779
|
const result = await this.requireClient().api.oneroster.assessmentResults.upsert(plan.attemptId, {
|
|
@@ -105956,9 +105790,42 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
105956
105790
|
grade: integration.grade
|
|
105957
105791
|
})
|
|
105958
105792
|
});
|
|
105959
|
-
|
|
105960
|
-
|
|
105961
|
-
|
|
105793
|
+
const [snapshot] = await Promise.all([
|
|
105794
|
+
this.isPlatformRoutedDiagnosticMetadata(metadata2) ? this.diagnosticSnapshot(result, metadata2, assessment, integration, {
|
|
105795
|
+
state: metadata2.diagnostic.state
|
|
105796
|
+
}) : this.snapshot(result, metadata2, assessment, integration),
|
|
105797
|
+
this.supersedeAttemptsBestEffort(db2, new Set(plan.supersedes))
|
|
105798
|
+
]);
|
|
105799
|
+
return snapshot;
|
|
105800
|
+
}
|
|
105801
|
+
attemptMatchesPlan(metadata2, plan) {
|
|
105802
|
+
const planned = buildPreparedAssessmentMetadata(plan, metadata2.startedAt, "");
|
|
105803
|
+
if (metadata2.selectedTest.assessmentKey !== planned.selectedTest.assessmentKey || metadata2.selectedTest.identifier !== planned.selectedTest.identifier || metadata2.selectedTest.contentRevision !== planned.selectedTest.contentRevision) {
|
|
105804
|
+
return false;
|
|
105805
|
+
}
|
|
105806
|
+
if (metadata2.purpose === "review" && planned.purpose === "review") {
|
|
105807
|
+
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);
|
|
105808
|
+
}
|
|
105809
|
+
if (metadata2.purpose === "mastery" && planned.purpose === "mastery") {
|
|
105810
|
+
return metadata2.mastery.requestFingerprint === planned.mastery.requestFingerprint;
|
|
105811
|
+
}
|
|
105812
|
+
if (metadata2.purpose === "diagnostic" && plan.purpose === "diagnostic") {
|
|
105813
|
+
return this.isPlatformRoutedDiagnosticMetadata(metadata2) && metadata2.diagnostic.definitionId === plan.diagnostic.definitionId && metadata2.diagnostic.routingRevision === plan.diagnostic.routingRevision;
|
|
105814
|
+
}
|
|
105815
|
+
return metadata2.purpose === planned.purpose;
|
|
105816
|
+
}
|
|
105817
|
+
async findAttemptResult(attemptId) {
|
|
105818
|
+
try {
|
|
105819
|
+
return await this.requireClient().api.oneroster.assessmentResults.get(attemptId);
|
|
105820
|
+
} catch (error88) {
|
|
105821
|
+
if (isApiError(error88) && error88.statusCode === 404) {
|
|
105822
|
+
return null;
|
|
105823
|
+
}
|
|
105824
|
+
throw error88;
|
|
105825
|
+
}
|
|
105826
|
+
}
|
|
105827
|
+
attemptInScope(result, metadata2, scope) {
|
|
105828
|
+
return metadata2.activityId === scope.activityId && metadata2.purpose === scope.purpose && metadata2.courseId === scope.courseId && metadata2.integrationId === scope.integrationId && result.student.sourcedId === scope.studentId;
|
|
105962
105829
|
}
|
|
105963
105830
|
async claimAttemptId(scope, from) {
|
|
105964
105831
|
for (let attemptNumber = from;attemptNumber < from + 20; attemptNumber += 1) {
|
|
@@ -105974,15 +105841,7 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
105974
105841
|
throw new Error(`Assessment attempt numbers ${from} through ${from + 19} are all occupied by records this code cannot read; refusing to overwrite them.`);
|
|
105975
105842
|
}
|
|
105976
105843
|
async attemptIdOccupied(attemptId) {
|
|
105977
|
-
|
|
105978
|
-
await this.requireClient().api.oneroster.assessmentResults.get(attemptId);
|
|
105979
|
-
} catch (error88) {
|
|
105980
|
-
if (isApiError(error88) && error88.statusCode === 404) {
|
|
105981
|
-
return false;
|
|
105982
|
-
}
|
|
105983
|
-
throw error88;
|
|
105984
|
-
}
|
|
105985
|
-
return true;
|
|
105844
|
+
return await this.findAttemptResult(attemptId) !== null;
|
|
105986
105845
|
}
|
|
105987
105846
|
replayCompletion(attempt, submissionId, params, game2) {
|
|
105988
105847
|
const { score, completion } = attempt.metadata;
|