@playcademy/sandbox 0.8.1-beta.6 → 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 +215 -385
- package/dist/server.js +215 -385
- package/package.json +1 -1
package/dist/server.js
CHANGED
|
@@ -1127,7 +1127,7 @@ var package_default;
|
|
|
1127
1127
|
var init_package = __esm(() => {
|
|
1128
1128
|
package_default = {
|
|
1129
1129
|
name: "@playcademy/sandbox",
|
|
1130
|
-
version: "0.8.1-beta.
|
|
1130
|
+
version: "0.8.1-beta.7",
|
|
1131
1131
|
description: "Local development server for Playcademy game development",
|
|
1132
1132
|
type: "module",
|
|
1133
1133
|
exports: {
|
|
@@ -10747,12 +10747,10 @@ function selectAssessmentCourseCandidate(candidates, subjectSpecified) {
|
|
|
10747
10747
|
if (candidates.length === 0) {
|
|
10748
10748
|
return { outcome: "none" };
|
|
10749
10749
|
}
|
|
10750
|
-
const
|
|
10751
|
-
if (
|
|
10750
|
+
const eligible = candidates.filter((candidate) => candidate.hasLiveTests);
|
|
10751
|
+
if (eligible.length === 0) {
|
|
10752
10752
|
return { outcome: "no_eligible_tests" };
|
|
10753
10753
|
}
|
|
10754
|
-
const withOpenAttempt = serviceable.filter((candidate) => candidate.hasOpenAttempt);
|
|
10755
|
-
const eligible = withOpenAttempt.length > 0 ? withOpenAttempt : serviceable;
|
|
10756
10754
|
const subjects = new Set(eligible.map((candidate) => candidate.subject)).size;
|
|
10757
10755
|
if (!subjectSpecified && subjects > 1) {
|
|
10758
10756
|
return { outcome: "ambiguous", subjects };
|
|
@@ -10764,12 +10762,9 @@ function assessmentAttemptTimestamp(value) {
|
|
|
10764
10762
|
const parsed = Date.parse(value);
|
|
10765
10763
|
return Number.isNaN(parsed) ? 0 : parsed;
|
|
10766
10764
|
}
|
|
10767
|
-
function newestAttempt(left, right, field) {
|
|
10768
|
-
const dateDifference = assessmentAttemptTimestamp(right[field]) - assessmentAttemptTimestamp(left[field]);
|
|
10769
|
-
return dateDifference || left.attemptId.localeCompare(right.attemptId);
|
|
10770
|
-
}
|
|
10771
10765
|
function compareNewestCompletedAssessmentAttempt(left, right) {
|
|
10772
|
-
|
|
10766
|
+
const dateDifference = assessmentAttemptTimestamp(right.scoreDate) - assessmentAttemptTimestamp(left.scoreDate);
|
|
10767
|
+
return dateDifference || left.attemptId.localeCompare(right.attemptId);
|
|
10773
10768
|
}
|
|
10774
10769
|
function compareAssessmentCatalogOrder(left, right) {
|
|
10775
10770
|
if (left.assessmentKey !== null && right.assessmentKey !== null) {
|
|
@@ -10792,31 +10787,22 @@ function orderRuntimeAssessmentTests(tests) {
|
|
|
10792
10787
|
return [...tests].toSorted(compareAssessmentCatalogOrder);
|
|
10793
10788
|
}
|
|
10794
10789
|
function selectRuntimeAssessment(liveTests, attempts) {
|
|
10795
|
-
const resumable = attempts.filter((attempt) => attempt.resumable !== false && (isAssessmentAttemptOpen(attempt) || isAssessmentAttemptAwaitingAward(attempt))).toSorted((left, right) => newestAttempt(left, right, "updatedAt"));
|
|
10796
|
-
if (resumable.length > 0) {
|
|
10797
|
-
return {
|
|
10798
|
-
kind: "resume",
|
|
10799
|
-
attempt: resumable[0],
|
|
10800
|
-
additionalResumableAttemptIds: resumable.slice(1).map((candidate) => candidate.attemptId)
|
|
10801
|
-
};
|
|
10802
|
-
}
|
|
10803
10790
|
const orderedTests = orderRuntimeAssessmentTests(liveTests);
|
|
10804
10791
|
if (orderedTests.length === 0) {
|
|
10805
10792
|
return null;
|
|
10806
10793
|
}
|
|
10807
|
-
const
|
|
10808
|
-
const exposedAssessmentKeys = new Set(
|
|
10794
|
+
const history = attempts.filter((attempt) => isAssessmentAttemptOpen(attempt) || isAssessmentAttemptAwaitingAward(attempt) || isAssessmentAttemptCompleted(attempt) || isAssessmentAttemptSuperseded(attempt));
|
|
10795
|
+
const exposedAssessmentKeys = new Set(history.map((attempt) => attempt.selectedAssessmentKey));
|
|
10809
10796
|
const unattempted = orderedTests.find((test) => !exposedAssessmentKeys.has(test.assessmentKey));
|
|
10810
10797
|
if (unattempted) {
|
|
10811
|
-
return {
|
|
10798
|
+
return { test: unattempted, reason: "unattempted" };
|
|
10812
10799
|
}
|
|
10813
|
-
const
|
|
10814
|
-
const latestTestIndex =
|
|
10800
|
+
const latest = history.toSorted(compareNewestCompletedAssessmentAttempt)[0];
|
|
10801
|
+
const latestTestIndex = latest ? orderedTests.findIndex((test) => test.assessmentKey === latest.selectedAssessmentKey) : -1;
|
|
10815
10802
|
if (latestTestIndex === -1) {
|
|
10816
|
-
return {
|
|
10803
|
+
return { test: orderedTests[0], reason: "first_live" };
|
|
10817
10804
|
}
|
|
10818
10805
|
return {
|
|
10819
|
-
kind: "start",
|
|
10820
10806
|
test: orderedTests[(latestTestIndex + 1) % orderedTests.length],
|
|
10821
10807
|
reason: "round_robin"
|
|
10822
10808
|
};
|
|
@@ -100351,13 +100337,6 @@ function buildAssessmentAwardRecord(input, mastery) {
|
|
|
100351
100337
|
...mastery.warnings ? { warnings: mastery.warnings } : {}
|
|
100352
100338
|
};
|
|
100353
100339
|
}
|
|
100354
|
-
function stageAssessmentAttemptSupersession(attempt) {
|
|
100355
|
-
Object.assign(attempt.result, ASSESSMENT_ATTEMPT_SUPERSEDED);
|
|
100356
|
-
Object.assign(attempt.selection, ASSESSMENT_ATTEMPT_SUPERSEDED);
|
|
100357
|
-
}
|
|
100358
|
-
function shouldConfirmAssessmentVersionUnavailable(details) {
|
|
100359
|
-
return typeof details === "object" && details !== null && "expectedRevision" in details && typeof details.expectedRevision === "string" && !("currentRevision" in details);
|
|
100360
|
-
}
|
|
100361
100340
|
function buildAssessmentAttemptSupersessionUpdate(result) {
|
|
100362
100341
|
if (result.status !== "active" || !isAssessmentAttemptOpen({
|
|
100363
100342
|
inProgress: result.inProgress ?? "",
|
|
@@ -100375,17 +100354,6 @@ function buildAssessmentAttemptSupersessionUpdate(result) {
|
|
|
100375
100354
|
metadata: result.metadata ?? {}
|
|
100376
100355
|
};
|
|
100377
100356
|
}
|
|
100378
|
-
async function resolveAvailableAssessmentAttempt(initialDecision, select3, resume) {
|
|
100379
|
-
let decision = initialDecision;
|
|
100380
|
-
while (decision?.kind === "resume") {
|
|
100381
|
-
const snapshot = await resume(decision);
|
|
100382
|
-
if (snapshot !== null) {
|
|
100383
|
-
return { kind: "resumed", snapshot };
|
|
100384
|
-
}
|
|
100385
|
-
decision = select3();
|
|
100386
|
-
}
|
|
100387
|
-
return { kind: "continue", decision };
|
|
100388
|
-
}
|
|
100389
100357
|
function diagnosticQuestionLineItemId(parentLineItemId, itemIdentifier) {
|
|
100390
100358
|
return deterministicUUID(["playcademy:diagnostic-question-line-item:v1", parentLineItemId, itemIdentifier].join("\x00"));
|
|
100391
100359
|
}
|
|
@@ -102686,21 +102654,6 @@ function assessmentPreparationFlightKey(input) {
|
|
|
102686
102654
|
async function assessmentPreparationReceiptMatches(plan, request) {
|
|
102687
102655
|
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);
|
|
102688
102656
|
}
|
|
102689
|
-
function resolveAssessmentPreparationSelection(decision, candidateCourseCount) {
|
|
102690
|
-
if (decision?.kind === "resume") {
|
|
102691
|
-
return {
|
|
102692
|
-
status: "blocked",
|
|
102693
|
-
result: { status: "canonical_required", reason: "resumable_attempt" }
|
|
102694
|
-
};
|
|
102695
|
-
}
|
|
102696
|
-
if (candidateCourseCount !== 1) {
|
|
102697
|
-
return {
|
|
102698
|
-
status: "blocked",
|
|
102699
|
-
result: { status: "unprepared", reason: "prerequisite_unavailable" }
|
|
102700
|
-
};
|
|
102701
|
-
}
|
|
102702
|
-
return decision ? { status: "ready", decision } : { status: "unavailable" };
|
|
102703
|
-
}
|
|
102704
102657
|
function preparedAssessmentIntegration(plan, candidates) {
|
|
102705
102658
|
if (candidates.rows.length !== 1) {
|
|
102706
102659
|
return null;
|
|
@@ -103446,15 +103399,16 @@ function assessmentReviewTranscriptExposures(input) {
|
|
|
103446
103399
|
for (const { result, metadata: metadata2 } of input.attempts) {
|
|
103447
103400
|
const state = { ...result, inProgress: result.inProgress ?? "" };
|
|
103448
103401
|
const settled = isAssessmentAttemptAwaitingAward(state) || isAssessmentAttemptCompleted(state);
|
|
103449
|
-
|
|
103402
|
+
const useTranscript = !settled && metadata2.transcript !== undefined;
|
|
103403
|
+
if (metadata2.purpose === "review" && (useTranscript || settled)) {
|
|
103450
103404
|
authoritative.add(result.sourcedId);
|
|
103451
103405
|
if (metadata2.review.bankRevision === input.bankRevision) {
|
|
103452
|
-
const accepted =
|
|
103453
|
-
itemIdentifier: submission.itemIdentifier,
|
|
103454
|
-
deliveredAt: submission.submittedAt
|
|
103455
|
-
})) : metadata2.transcript.administrations.map((entry) => ({
|
|
103406
|
+
const accepted = useTranscript ? metadata2.transcript.administrations.map((entry) => ({
|
|
103456
103407
|
itemIdentifier: entry.itemIdentifier,
|
|
103457
103408
|
deliveredAt: metadata2.updatedAt
|
|
103409
|
+
})) : metadata2.itemSubmissions.map((submission) => ({
|
|
103410
|
+
itemIdentifier: submission.itemIdentifier,
|
|
103411
|
+
deliveredAt: submission.submittedAt
|
|
103458
103412
|
}));
|
|
103459
103413
|
exposures.push(...accepted);
|
|
103460
103414
|
}
|
|
@@ -103588,7 +103542,6 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
103588
103542
|
static PREPARATION_IN_FLIGHT_LIMIT = 32;
|
|
103589
103543
|
static ASSESSMENT_CACHE_TTL_MS = 60000;
|
|
103590
103544
|
static EXPORT_CONCURRENCY = 4;
|
|
103591
|
-
static ASSESSMENT_VERSION_CONFIRMATION_DELAY_MS = 25;
|
|
103592
103545
|
static SCORING_CONCURRENCY = 4;
|
|
103593
103546
|
static STALE_AWARD_AGE_MS = 15 * 60 * 1000;
|
|
103594
103547
|
deps;
|
|
@@ -103678,8 +103631,6 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
103678
103631
|
return this.startReview({ gameId, studentId, input, user });
|
|
103679
103632
|
}
|
|
103680
103633
|
const masteryStandard = input.purpose === "mastery" ? normalizeMasteryStandard(input.standard) : undefined;
|
|
103681
|
-
const requestFingerprint = this.startRequestFingerprint(input);
|
|
103682
|
-
const pendingSupersessions = new Set;
|
|
103683
103634
|
const candidates = await this.candidateIntegrations(gameId, studentId, input);
|
|
103684
103635
|
const lockKeys = candidates.rows.map((row) => assessmentRuntimeStartLockKey({
|
|
103685
103636
|
studentId,
|
|
@@ -103688,7 +103639,7 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
103688
103639
|
courseId: row.courseId
|
|
103689
103640
|
}));
|
|
103690
103641
|
return this.deps.assessmentRuntimeLock(this.deps.db, lockKeys, async (db2) => {
|
|
103691
|
-
const context2 = await this.selectIntegration(candidates,
|
|
103642
|
+
const context2 = await this.selectIntegration(candidates, input);
|
|
103692
103643
|
const attemptScope = {
|
|
103693
103644
|
studentId,
|
|
103694
103645
|
activityId: input.activityId,
|
|
@@ -103701,39 +103652,28 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
103701
103652
|
this.liveTests(context2.integration.id, input.purpose, db2, masteryStandard),
|
|
103702
103653
|
this.listAttempts(attemptScope)
|
|
103703
103654
|
]);
|
|
103704
|
-
const
|
|
103705
|
-
|
|
103706
|
-
|
|
103707
|
-
|
|
103708
|
-
|
|
103709
|
-
}
|
|
103710
|
-
|
|
103711
|
-
|
|
103712
|
-
|
|
103713
|
-
|
|
103714
|
-
|
|
103715
|
-
|
|
103716
|
-
|
|
103717
|
-
|
|
103718
|
-
|
|
103719
|
-
|
|
103720
|
-
if (assessment.identifier !== decision.test.qtiTestIdentifier) {
|
|
103721
|
-
assessment = await this.loadAssessment(decision.test.qtiTestIdentifier);
|
|
103722
|
-
}
|
|
103723
|
-
const classId = await this.resolveClassId(context2.integration.courseId, context2.enrollment, studentId);
|
|
103655
|
+
const pending = await this.pendingAwardSnapshot(listing, context2);
|
|
103656
|
+
if (pending) {
|
|
103657
|
+
return pending;
|
|
103658
|
+
}
|
|
103659
|
+
const decision = this.selectFreshStart(tests, listing, input.purpose);
|
|
103660
|
+
const [assessment, classId, { attemptNumber, attemptId }] = await Promise.all([
|
|
103661
|
+
this.loadAssessment(decision.test.qtiTestIdentifier),
|
|
103662
|
+
this.resolveClassId(context2.integration.courseId, context2.enrollment, studentId),
|
|
103663
|
+
this.claimAttemptId({
|
|
103664
|
+
gameId,
|
|
103665
|
+
studentId,
|
|
103666
|
+
courseId: context2.integration.courseId,
|
|
103667
|
+
activityId: input.activityId,
|
|
103668
|
+
purpose: input.purpose
|
|
103669
|
+
}, nextAssessmentAttemptNumber(listing.attempts.values()))
|
|
103670
|
+
]);
|
|
103724
103671
|
const lineItemId = await this.ensureLineItem({
|
|
103725
103672
|
classId,
|
|
103726
103673
|
integration: context2.integration,
|
|
103727
103674
|
purpose: input.purpose,
|
|
103728
103675
|
assessment
|
|
103729
103676
|
});
|
|
103730
|
-
const { attemptNumber, attemptId } = await this.claimAttemptId({
|
|
103731
|
-
gameId,
|
|
103732
|
-
studentId,
|
|
103733
|
-
courseId: context2.integration.courseId,
|
|
103734
|
-
activityId: input.activityId,
|
|
103735
|
-
purpose: input.purpose
|
|
103736
|
-
}, Math.max(0, ...[...refreshed.values()].map((entry) => entry.metadata.attemptNumber)) + 1);
|
|
103737
103677
|
const timestamp6 = new Date().toISOString();
|
|
103738
103678
|
const metadataBase = {
|
|
103739
103679
|
version: PLAYCADEMY_ASSESSMENT_RESULT_METADATA_VERSION,
|
|
@@ -103758,7 +103698,7 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
103758
103698
|
...metadataBase,
|
|
103759
103699
|
purpose: input.purpose,
|
|
103760
103700
|
mastery: {
|
|
103761
|
-
requestFingerprint,
|
|
103701
|
+
requestFingerprint: masteryRequestFingerprint(input),
|
|
103762
103702
|
standard: masteryStandard
|
|
103763
103703
|
}
|
|
103764
103704
|
} : { ...metadataBase, purpose: input.purpose };
|
|
@@ -103776,8 +103716,11 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
103776
103716
|
grade: context2.integration.grade
|
|
103777
103717
|
})
|
|
103778
103718
|
});
|
|
103779
|
-
await
|
|
103780
|
-
|
|
103719
|
+
const [snapshot] = await Promise.all([
|
|
103720
|
+
this.snapshot(result, metadata2, assessment, context2.integration),
|
|
103721
|
+
this.supersedeReplacedAttempts(db2, listing.attempts, context2.enrollment.id)
|
|
103722
|
+
]);
|
|
103723
|
+
return snapshot;
|
|
103781
103724
|
});
|
|
103782
103725
|
}
|
|
103783
103726
|
async prepareNewAttempt(params, requestHash) {
|
|
@@ -103786,7 +103729,7 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
103786
103729
|
this.candidateIntegrations(params.gameId, params.studentId, params.input),
|
|
103787
103730
|
this.studentAssessmentResults(params.studentId)
|
|
103788
103731
|
]);
|
|
103789
|
-
const context2 = await this.selectIntegration(candidates, params.
|
|
103732
|
+
const context2 = await this.selectIntegration(candidates, params.input);
|
|
103790
103733
|
const attemptScope = {
|
|
103791
103734
|
studentId: params.studentId,
|
|
103792
103735
|
activityId: params.input.activityId,
|
|
@@ -103797,40 +103740,30 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
103797
103740
|
};
|
|
103798
103741
|
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);
|
|
103799
103742
|
const listing = await this.attemptListing(results, attemptScope);
|
|
103800
|
-
|
|
103801
|
-
|
|
103802
|
-
|
|
103803
|
-
const selection = resolveAssessmentPreparationSelection(decision, candidates.rows.length);
|
|
103804
|
-
if (selection.status === "blocked") {
|
|
103805
|
-
this.recordPreparation(selection.result.status, params.input.purpose, startedAt, {
|
|
103806
|
-
reason: selection.result.status === "canonical_required" ? selection.result.reason : "multiple_candidate_courses"
|
|
103743
|
+
if (this.pendingAwardAttempt(listing, context2.enrollment.id)) {
|
|
103744
|
+
this.recordPreparation("unprepared", params.input.purpose, startedAt, {
|
|
103745
|
+
reason: "pending_award"
|
|
103807
103746
|
});
|
|
103808
|
-
return
|
|
103747
|
+
return { status: "unprepared", reason: "prerequisite_unavailable" };
|
|
103809
103748
|
}
|
|
103810
|
-
if (
|
|
103811
|
-
|
|
103749
|
+
if (candidates.rows.length !== 1) {
|
|
103750
|
+
this.recordPreparation("unprepared", params.input.purpose, startedAt, {
|
|
103751
|
+
reason: "multiple_candidate_courses"
|
|
103752
|
+
});
|
|
103753
|
+
return { status: "unprepared", reason: "prerequisite_unavailable" };
|
|
103812
103754
|
}
|
|
103813
103755
|
const preparation = {
|
|
103814
103756
|
context: context2,
|
|
103815
103757
|
tests,
|
|
103816
103758
|
listing,
|
|
103817
|
-
decision:
|
|
103759
|
+
decision: this.selectFreshStart(tests, listing, params.input.purpose)
|
|
103818
103760
|
};
|
|
103819
103761
|
let prepared;
|
|
103820
103762
|
try {
|
|
103821
103763
|
prepared = await this.buildPreparationPlan(params, requestHash, preparation);
|
|
103822
|
-
|
|
103823
|
-
if (!assessment) {
|
|
103764
|
+
if (!await this.loadPreparedPlanAssessment(prepared.plan)) {
|
|
103824
103765
|
throw new Error("Prepared assessment artifact is unavailable");
|
|
103825
103766
|
}
|
|
103826
|
-
await this.provisionPreparationPrerequisites(() => this.ensureTranscriptLineItems({
|
|
103827
|
-
version: 1,
|
|
103828
|
-
gameId: prepared.plan.gameId,
|
|
103829
|
-
studentId: prepared.plan.studentId,
|
|
103830
|
-
attemptId: prepared.plan.attemptId,
|
|
103831
|
-
parentLineItemId: prepared.plan.parentLineItemId,
|
|
103832
|
-
metadata: buildPreparedAssessmentMetadata(prepared.plan, new Date().toISOString(), assessment.title)
|
|
103833
|
-
}, assessment));
|
|
103834
103767
|
} catch (error88) {
|
|
103835
103768
|
if (error88 instanceof ValidationError || error88 instanceof AssessmentRuntimeError) {
|
|
103836
103769
|
throw error88;
|
|
@@ -103919,6 +103852,7 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
103919
103852
|
courseName: context2.enrollment.course.title,
|
|
103920
103853
|
...attempt,
|
|
103921
103854
|
parentLineItemId,
|
|
103855
|
+
supersedes: [...this.replacedAttemptIds(listing.attempts, context2.enrollment.id)],
|
|
103922
103856
|
selectedTest: {
|
|
103923
103857
|
assessmentKey: decision.test.assessmentKey,
|
|
103924
103858
|
identifier: assessment.identifier,
|
|
@@ -103970,26 +103904,12 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
103970
103904
|
throw AssessmentRuntimeError.from(assessmentReviewBankShortage(selected.fulfillment));
|
|
103971
103905
|
}
|
|
103972
103906
|
const artifact = await this.loadOrCompileReviewBankArtifact(catalog);
|
|
103973
|
-
const assessment = projectReviewAssessment(artifact.assessment, catalog.bank, selected.selections);
|
|
103974
103907
|
const source = { assessment: artifact.assessment, bank: catalog.bank };
|
|
103975
103908
|
const parentLineItemId = await this.provisionPreparationPrerequisites(() => this.ensureReviewBankLineItem({
|
|
103976
103909
|
integration: context2.integration,
|
|
103977
103910
|
activityId: params.input.activityId,
|
|
103978
103911
|
source
|
|
103979
103912
|
}));
|
|
103980
|
-
await this.provisionPreparationPrerequisites(() => runWithConcurrency(assessment.items, TimebackAssessmentRuntimeService.SCORING_CONCURRENCY, (item) => {
|
|
103981
|
-
const indexed = catalog.bank.items.find((candidate) => candidate.itemIdentifier === item.identifier);
|
|
103982
|
-
if (!indexed) {
|
|
103983
|
-
throw new Error(`Review-bank index is missing item ${item.identifier}`);
|
|
103984
|
-
}
|
|
103985
|
-
return this.ensureReviewQuestionLineItem({
|
|
103986
|
-
parentLineItemId,
|
|
103987
|
-
integration: context2.integration,
|
|
103988
|
-
bankRevision: catalog.bank.bankRevision,
|
|
103989
|
-
standards: [...indexed.standards],
|
|
103990
|
-
item
|
|
103991
|
-
});
|
|
103992
|
-
}));
|
|
103993
103913
|
const attempt = await this.provisionPreparationPrerequisites(() => this.claimAttemptId({
|
|
103994
103914
|
gameId: params.gameId,
|
|
103995
103915
|
studentId: params.studentId,
|
|
@@ -104011,6 +103931,7 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
104011
103931
|
courseName: context2.enrollment.course.title,
|
|
104012
103932
|
...attempt,
|
|
104013
103933
|
parentLineItemId,
|
|
103934
|
+
supersedes: [...this.replacedAttemptIds(listing.attempts, context2.enrollment.id)],
|
|
104014
103935
|
selectedTest: {
|
|
104015
103936
|
assessmentKey: decision.test.assessmentKey,
|
|
104016
103937
|
identifier: artifact.assessment.identifier,
|
|
@@ -104059,10 +103980,7 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
104059
103980
|
if (!integration || !await this.preparedPlanTestIsLive(verified, db2)) {
|
|
104060
103981
|
return null;
|
|
104061
103982
|
}
|
|
104062
|
-
|
|
104063
|
-
return null;
|
|
104064
|
-
}
|
|
104065
|
-
return this.createPreparedAttempt(verified, assessment, integration);
|
|
103983
|
+
return this.claimPreparedAttempt(db2, verified, assessment, integration);
|
|
104066
103984
|
});
|
|
104067
103985
|
if (!outcome) {
|
|
104068
103986
|
this.recordPreparedStart("fallback", params.input.purpose, startedAt, {
|
|
@@ -104087,8 +104005,6 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
104087
104005
|
studentId,
|
|
104088
104006
|
input
|
|
104089
104007
|
}) {
|
|
104090
|
-
const requestFingerprint = this.startRequestFingerprint(input);
|
|
104091
|
-
const pendingSupersessions = new Set;
|
|
104092
104008
|
const candidates = await this.candidateIntegrations(gameId, studentId, input);
|
|
104093
104009
|
const lockKeys = candidates.rows.map((row) => assessmentRuntimeStartLockKey({
|
|
104094
104010
|
studentId,
|
|
@@ -104097,7 +104013,7 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
104097
104013
|
courseId: row.courseId
|
|
104098
104014
|
}));
|
|
104099
104015
|
return this.deps.assessmentRuntimeLock(this.deps.db, lockKeys, async (db2) => {
|
|
104100
|
-
const context2 = await this.selectIntegration(candidates,
|
|
104016
|
+
const context2 = await this.selectIntegration(candidates, input);
|
|
104101
104017
|
const attemptScope = {
|
|
104102
104018
|
studentId,
|
|
104103
104019
|
activityId: input.activityId,
|
|
@@ -104110,32 +104026,25 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
104110
104026
|
this.liveTests(context2.integration.id, input.purpose, db2, undefined, input.diagnosticKey),
|
|
104111
104027
|
this.listAttempts(attemptScope)
|
|
104112
104028
|
]);
|
|
104113
|
-
const
|
|
104114
|
-
if (
|
|
104115
|
-
|
|
104116
|
-
return initialResolution.snapshot;
|
|
104029
|
+
const pending = await this.pendingAwardSnapshot(listing, context2);
|
|
104030
|
+
if (pending) {
|
|
104031
|
+
return pending;
|
|
104117
104032
|
}
|
|
104118
|
-
|
|
104033
|
+
const decision = this.selectFreshStart(tests, listing, input.purpose);
|
|
104119
104034
|
let definition = await this.requireDiagnosticDefinition(decision.test.id, context2.integration.id, db2, true);
|
|
104120
|
-
|
|
104121
|
-
|
|
104122
|
-
|
|
104123
|
-
|
|
104124
|
-
|
|
104125
|
-
|
|
104126
|
-
|
|
104127
|
-
|
|
104128
|
-
|
|
104129
|
-
|
|
104130
|
-
|
|
104131
|
-
|
|
104132
|
-
|
|
104133
|
-
routing = await this.initializeHostedDiagnostic(definition, assessment);
|
|
104134
|
-
} else {
|
|
104135
|
-
definition = await this.requireDiagnosticDefinition(definition.id, context2.integration.id, db2, true);
|
|
104136
|
-
routing = await this.initializeHostedDiagnostic(definition, assessment);
|
|
104137
|
-
}
|
|
104138
|
-
const classId = await this.resolveClassId(context2.integration.courseId, context2.enrollment, studentId);
|
|
104035
|
+
const [assessment, classId, { attemptNumber, attemptId }] = await Promise.all([
|
|
104036
|
+
this.loadAssessment(definition.qtiTestIdentifier),
|
|
104037
|
+
this.resolveClassId(context2.integration.courseId, context2.enrollment, studentId),
|
|
104038
|
+
this.claimAttemptId({
|
|
104039
|
+
gameId,
|
|
104040
|
+
studentId,
|
|
104041
|
+
courseId: context2.integration.courseId,
|
|
104042
|
+
activityId: input.activityId,
|
|
104043
|
+
purpose: input.purpose
|
|
104044
|
+
}, nextAssessmentAttemptNumber(listing.attempts.values()))
|
|
104045
|
+
]);
|
|
104046
|
+
definition = await this.requireDiagnosticDefinition(definition.id, context2.integration.id, db2, true);
|
|
104047
|
+
const routing = await this.initializeHostedDiagnostic(definition, assessment);
|
|
104139
104048
|
const lineItemId = await this.ensureLineItem({
|
|
104140
104049
|
classId,
|
|
104141
104050
|
integration: context2.integration,
|
|
@@ -104147,13 +104056,6 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
104147
104056
|
routingRevision: routing.routingRevision
|
|
104148
104057
|
}
|
|
104149
104058
|
});
|
|
104150
|
-
const { attemptNumber, attemptId } = await this.claimAttemptId({
|
|
104151
|
-
gameId,
|
|
104152
|
-
studentId,
|
|
104153
|
-
courseId: context2.integration.courseId,
|
|
104154
|
-
activityId: input.activityId,
|
|
104155
|
-
purpose: input.purpose
|
|
104156
|
-
}, Math.max(0, ...[...refreshed.values()].map((entry) => entry.metadata.attemptNumber)) + 1);
|
|
104157
104059
|
const timestamp6 = new Date().toISOString();
|
|
104158
104060
|
const metadata2 = {
|
|
104159
104061
|
version: PLAYCADEMY_ASSESSMENT_RESULT_METADATA_VERSION,
|
|
@@ -104197,10 +104099,13 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
104197
104099
|
grade: context2.integration.grade
|
|
104198
104100
|
})
|
|
104199
104101
|
});
|
|
104200
|
-
await
|
|
104201
|
-
|
|
104202
|
-
|
|
104203
|
-
|
|
104102
|
+
const [snapshot] = await Promise.all([
|
|
104103
|
+
this.diagnosticSnapshot(result, metadata2, assessment, context2.integration, {
|
|
104104
|
+
state: routing.state
|
|
104105
|
+
}),
|
|
104106
|
+
this.supersedeReplacedAttempts(db2, listing.attempts, context2.enrollment.id)
|
|
104107
|
+
]);
|
|
104108
|
+
return snapshot;
|
|
104204
104109
|
});
|
|
104205
104110
|
}
|
|
104206
104111
|
async startReview({
|
|
@@ -104210,7 +104115,6 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
104210
104115
|
}) {
|
|
104211
104116
|
const preparationStartedAt = Date.now();
|
|
104212
104117
|
const requestFingerprint = reviewRequestFingerprint(input, DEFAULT_REVIEW_SELECTION_POLICY);
|
|
104213
|
-
const pendingSupersessions = new Set;
|
|
104214
104118
|
const normalizedRequest = normalizeReviewRequest(input);
|
|
104215
104119
|
const candidates = await this.candidateIntegrations(gameId, studentId, input);
|
|
104216
104120
|
const lockKeys = candidates.rows.map((row) => assessmentRuntimeStartLockKey({
|
|
@@ -104220,7 +104124,7 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
104220
104124
|
courseId: row.courseId
|
|
104221
104125
|
}));
|
|
104222
104126
|
return this.deps.assessmentRuntimeLock(this.deps.db, lockKeys, async (db2) => {
|
|
104223
|
-
const context2 = await this.selectIntegration(candidates,
|
|
104127
|
+
const context2 = await this.selectIntegration(candidates, input);
|
|
104224
104128
|
const attemptScope = {
|
|
104225
104129
|
studentId,
|
|
104226
104130
|
activityId: input.activityId,
|
|
@@ -104233,42 +104137,22 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
104233
104137
|
this.liveTests(context2.integration.id, input.purpose, db2),
|
|
104234
104138
|
this.listAttempts(attemptScope)
|
|
104235
104139
|
]);
|
|
104236
|
-
const
|
|
104237
|
-
|
|
104238
|
-
|
|
104239
|
-
await this.persistPendingSupersessionsBestEffort(db2, pendingSupersessions);
|
|
104240
|
-
return initialResolution.snapshot;
|
|
104241
|
-
}
|
|
104242
|
-
let decision = initialResolution.decision;
|
|
104243
|
-
if (tests.length === 0 || !decision) {
|
|
104244
|
-
throw AssessmentRuntimeError.from(assessmentNoEligibleTests(input.purpose));
|
|
104140
|
+
const pending = await this.pendingAwardSnapshot(listing, context2);
|
|
104141
|
+
if (pending) {
|
|
104142
|
+
return pending;
|
|
104245
104143
|
}
|
|
104144
|
+
const decision = this.selectFreshStart(tests, listing, input.purpose);
|
|
104246
104145
|
if (tests.length > 1) {
|
|
104247
104146
|
throw new ValidationError("Standards review requires exactly one live review-bank assessment.");
|
|
104248
104147
|
}
|
|
104249
104148
|
const manifestStartedAt = Date.now();
|
|
104250
|
-
|
|
104149
|
+
const catalog = await this.loadReviewBankCatalog(decision.test.qtiTestIdentifier);
|
|
104251
104150
|
this.recordReviewPreparationPhase("manifest_load", manifestStartedAt, {
|
|
104252
104151
|
candidateCount: catalog.bank.items.length
|
|
104253
104152
|
});
|
|
104254
|
-
const refreshedListing = await this.listAttempts(attemptScope);
|
|
104255
|
-
const refreshed = refreshedListing.attempts;
|
|
104256
|
-
const refreshedResolution = await this.resumeAvailableAttempt(tests, refreshed, requestFingerprint, pendingSupersessions, (resumeDecision) => this.resumeReviewSnapshot(resumeDecision, refreshed, context2));
|
|
104257
|
-
if (refreshedResolution.kind === "resumed") {
|
|
104258
|
-
await this.persistPendingSupersessionsBestEffort(db2, pendingSupersessions);
|
|
104259
|
-
return refreshedResolution.snapshot;
|
|
104260
|
-
}
|
|
104261
|
-
const refreshedDecision = refreshedResolution.decision;
|
|
104262
|
-
if (!refreshedDecision || refreshedDecision.kind !== "start") {
|
|
104263
|
-
throw AssessmentRuntimeError.from(assessmentNoEligibleTests(input.purpose));
|
|
104264
|
-
}
|
|
104265
|
-
decision = refreshedDecision;
|
|
104266
|
-
if (catalog.test.identifier !== decision.test.qtiTestIdentifier) {
|
|
104267
|
-
catalog = await this.loadReviewBankCatalog(decision.test.qtiTestIdentifier);
|
|
104268
|
-
}
|
|
104269
104153
|
const exposures = assessmentReviewTranscriptExposures({
|
|
104270
|
-
attempts:
|
|
104271
|
-
children:
|
|
104154
|
+
attempts: listing.attempts.values(),
|
|
104155
|
+
children: listing.reviewChildren.values(),
|
|
104272
104156
|
bankRevision: catalog.bank.bankRevision
|
|
104273
104157
|
});
|
|
104274
104158
|
const selected = selectReviewItems({
|
|
@@ -104289,18 +104173,20 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
104289
104173
|
});
|
|
104290
104174
|
const assessment = projectReviewAssessment(source.assessment, source.bank, selected.selections);
|
|
104291
104175
|
const provisioningStartedAt = Date.now();
|
|
104292
|
-
const parentLineItemId = await
|
|
104293
|
-
|
|
104294
|
-
|
|
104295
|
-
|
|
104296
|
-
|
|
104297
|
-
|
|
104298
|
-
|
|
104299
|
-
|
|
104300
|
-
|
|
104301
|
-
|
|
104302
|
-
|
|
104303
|
-
|
|
104176
|
+
const [parentLineItemId, { attemptNumber, attemptId }] = await Promise.all([
|
|
104177
|
+
this.ensureReviewBankLineItem({
|
|
104178
|
+
integration: context2.integration,
|
|
104179
|
+
activityId: input.activityId,
|
|
104180
|
+
source
|
|
104181
|
+
}),
|
|
104182
|
+
this.claimAttemptId({
|
|
104183
|
+
gameId,
|
|
104184
|
+
studentId,
|
|
104185
|
+
courseId: context2.integration.courseId,
|
|
104186
|
+
activityId: input.activityId,
|
|
104187
|
+
purpose: input.purpose
|
|
104188
|
+
}, nextAssessmentAttemptNumber(listing.attempts.values()))
|
|
104189
|
+
]);
|
|
104304
104190
|
const timestamp6 = new Date().toISOString();
|
|
104305
104191
|
const metadata2 = {
|
|
104306
104192
|
version: PLAYCADEMY_ASSESSMENT_RESULT_METADATA_VERSION,
|
|
@@ -104344,13 +104230,16 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
104344
104230
|
grade: context2.integration.grade
|
|
104345
104231
|
})
|
|
104346
104232
|
});
|
|
104347
|
-
await
|
|
104233
|
+
const [snapshot] = await Promise.all([
|
|
104234
|
+
this.snapshot(result, metadata2, assessment, context2.integration),
|
|
104235
|
+
this.supersedeReplacedAttempts(db2, listing.attempts, context2.enrollment.id)
|
|
104236
|
+
]);
|
|
104348
104237
|
this.recordReviewPreparationPhase("provisioning", provisioningStartedAt);
|
|
104349
104238
|
this.recordReviewPreparationPhase("total", preparationStartedAt, {
|
|
104350
104239
|
candidateCount: catalog.bank.items.length,
|
|
104351
104240
|
selectedCount: selected.selections.length
|
|
104352
104241
|
});
|
|
104353
|
-
return
|
|
104242
|
+
return snapshot;
|
|
104354
104243
|
});
|
|
104355
104244
|
}
|
|
104356
104245
|
async get(params) {
|
|
@@ -104868,28 +104757,11 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
104868
104757
|
liveTestIntegrationIds: integrationIdsWithLiveTests
|
|
104869
104758
|
};
|
|
104870
104759
|
}
|
|
104871
|
-
async selectIntegration(candidates,
|
|
104872
|
-
const {
|
|
104873
|
-
rows: candidateRows,
|
|
104874
|
-
enrollmentByCourse,
|
|
104875
|
-
liveTestIntegrationIds: integrationIdsWithLiveTests
|
|
104876
|
-
} = candidates;
|
|
104877
|
-
const needsOpenAnnotation = candidateRows.length > 1 || candidateRows.some((row) => !integrationIdsWithLiveTests.has(row.id));
|
|
104878
|
-
const openAttemptScope = {
|
|
104879
|
-
studentId,
|
|
104880
|
-
activityId: input.activityId,
|
|
104881
|
-
purpose: input.purpose,
|
|
104882
|
-
enrollmentIdByCourse: new Map(candidateRows.map((row) => [row.courseId, enrollmentByCourse.get(row.courseId).id])),
|
|
104883
|
-
requestFingerprint: this.startRequestFingerprint(input)
|
|
104884
|
-
};
|
|
104885
|
-
let openAttemptCourseIds = new Set;
|
|
104886
|
-
if (needsOpenAnnotation) {
|
|
104887
|
-
openAttemptCourseIds = results ? this.openAttemptCourseIdsFromResults(results, openAttemptScope) : await this.openAttemptCourseIds(openAttemptScope);
|
|
104888
|
-
}
|
|
104760
|
+
async selectIntegration(candidates, input) {
|
|
104761
|
+
const { rows: candidateRows, enrollmentByCourse, liveTestIntegrationIds } = candidates;
|
|
104889
104762
|
const annotated = candidateRows.map((row) => ({
|
|
104890
104763
|
...row,
|
|
104891
|
-
hasLiveTests:
|
|
104892
|
-
hasOpenAttempt: openAttemptCourseIds.has(row.courseId)
|
|
104764
|
+
hasLiveTests: liveTestIntegrationIds.has(row.id)
|
|
104893
104765
|
}));
|
|
104894
104766
|
const selection = selectAssessmentCourseCandidate(annotated, Boolean(input.subject));
|
|
104895
104767
|
if (selection.outcome === "none") {
|
|
@@ -104918,9 +104790,6 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
104918
104790
|
standard: assessmentStandardForRow(row)
|
|
104919
104791
|
})).filter((test) => !matchesMasteryStandard || matchesMasteryStandard(test.standard));
|
|
104920
104792
|
}
|
|
104921
|
-
async openAttemptCourseIds(scope) {
|
|
104922
|
-
return this.openAttemptCourseIdsFromResults(await this.studentAssessmentResults(scope.studentId), scope);
|
|
104923
|
-
}
|
|
104924
104793
|
async studentAssessmentResults(studentId) {
|
|
104925
104794
|
const stream = this.requireClient().api.oneroster.assessmentResults.stream({
|
|
104926
104795
|
where: {
|
|
@@ -104934,16 +104803,6 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
104934
104803
|
}
|
|
104935
104804
|
return results;
|
|
104936
104805
|
}
|
|
104937
|
-
openAttemptCourseIdsFromResults(results, scope) {
|
|
104938
|
-
const courseIds = new Set;
|
|
104939
|
-
for (const result of results) {
|
|
104940
|
-
const metadata2 = playcademyAssessmentResultMetadata(result.metadata);
|
|
104941
|
-
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))) {
|
|
104942
|
-
courseIds.add(metadata2.courseId);
|
|
104943
|
-
}
|
|
104944
|
-
}
|
|
104945
|
-
return courseIds;
|
|
104946
|
-
}
|
|
104947
104806
|
async listAttempts(scope) {
|
|
104948
104807
|
return this.attemptListing(await this.studentAssessmentResults(scope.studentId), scope);
|
|
104949
104808
|
}
|
|
@@ -104974,7 +104833,7 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
104974
104833
|
}
|
|
104975
104834
|
}
|
|
104976
104835
|
}
|
|
104977
|
-
if (metadata2 &&
|
|
104836
|
+
if (metadata2 && this.attemptInScope(result, metadata2, scope)) {
|
|
104978
104837
|
const selectedAssessmentKey = metadata2.selectedTest.assessmentKey ?? assessmentKeyFromManagedQtiIdentifier(metadata2.selectedTest.identifier);
|
|
104979
104838
|
if (selectedAssessmentKey) {
|
|
104980
104839
|
attempts.set(result.sourcedId, {
|
|
@@ -104983,12 +104842,9 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
104983
104842
|
selection: {
|
|
104984
104843
|
attemptId: result.sourcedId,
|
|
104985
104844
|
selectedAssessmentKey,
|
|
104986
|
-
selectedTestIdentifier: metadata2.selectedTest.identifier,
|
|
104987
104845
|
inProgress: result.inProgress ?? "",
|
|
104988
104846
|
scoreStatus: result.scoreStatus,
|
|
104989
|
-
scoreDate: result.scoreDate
|
|
104990
|
-
updatedAt: result.dateLastModified ?? metadata2.updatedAt,
|
|
104991
|
-
resumable: metadata2.enrollmentId === scope.enrollmentId
|
|
104847
|
+
scoreDate: result.scoreDate
|
|
104992
104848
|
}
|
|
104993
104849
|
});
|
|
104994
104850
|
}
|
|
@@ -104996,114 +104852,61 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
104996
104852
|
}
|
|
104997
104853
|
return { attempts, reviewChildren };
|
|
104998
104854
|
}
|
|
104999
|
-
|
|
105000
|
-
|
|
105001
|
-
return attempts;
|
|
105002
|
-
}
|
|
105003
|
-
return new Map([...attempts].filter(([, attempt]) => this.attemptMatchesRequestFingerprint(attempt.metadata, requestFingerprint)));
|
|
105004
|
-
}
|
|
105005
|
-
startRequestFingerprint(input) {
|
|
105006
|
-
if (input.purpose === "review") {
|
|
105007
|
-
return reviewRequestFingerprint(input, DEFAULT_REVIEW_SELECTION_POLICY);
|
|
105008
|
-
}
|
|
105009
|
-
if (input.purpose === "mastery") {
|
|
105010
|
-
return masteryRequestFingerprint(input);
|
|
105011
|
-
}
|
|
105012
|
-
return input.purpose === "diagnostic" ? input.diagnosticKey : undefined;
|
|
105013
|
-
}
|
|
105014
|
-
attemptMatchesRequestFingerprint(metadata2, requestFingerprint) {
|
|
105015
|
-
if (metadata2.purpose === "review") {
|
|
105016
|
-
return metadata2.review.requestFingerprint === requestFingerprint;
|
|
105017
|
-
}
|
|
105018
|
-
if (metadata2.purpose === "mastery") {
|
|
105019
|
-
return metadata2.mastery.requestFingerprint === requestFingerprint;
|
|
105020
|
-
}
|
|
105021
|
-
if (metadata2.purpose === "diagnostic" && requestFingerprint !== undefined) {
|
|
105022
|
-
return this.isPlatformRoutedDiagnosticMetadata(metadata2) && metadata2.diagnostic.diagnosticKey === requestFingerprint;
|
|
105023
|
-
}
|
|
105024
|
-
return true;
|
|
105025
|
-
}
|
|
105026
|
-
requireDecision(decision, purpose) {
|
|
104855
|
+
selectFreshStart(tests, listing, purpose) {
|
|
104856
|
+
const decision = selectRuntimeAssessment(tests, [...listing.attempts.values()].map((entry) => entry.selection));
|
|
105027
104857
|
if (!decision) {
|
|
105028
104858
|
throw AssessmentRuntimeError.from(assessmentNoEligibleTests(purpose));
|
|
105029
104859
|
}
|
|
105030
104860
|
return decision;
|
|
105031
104861
|
}
|
|
105032
|
-
|
|
105033
|
-
|
|
105034
|
-
|
|
105035
|
-
"app.assessment.attempt_id": decision.attempt.attemptId,
|
|
105036
|
-
"app.assessment.additional_attempt_ids": decision.additionalResumableAttemptIds.join(",")
|
|
105037
|
-
});
|
|
105038
|
-
}
|
|
105039
|
-
const resumed = attempts.get(decision.attempt.attemptId);
|
|
105040
|
-
return this.snapshotForResult(resumed.result, resumed.metadata, integration);
|
|
105041
|
-
}
|
|
105042
|
-
async resumeDiagnosticSnapshot(decision, attempts, integration, _db = this.deps.db) {
|
|
105043
|
-
const resumed = attempts.get(decision.attempt.attemptId);
|
|
105044
|
-
return this.snapshotForResult(resumed.result, resumed.metadata, integration);
|
|
104862
|
+
pendingAwardAttempt(listing, enrollmentId) {
|
|
104863
|
+
const pending = [...listing.attempts.values()].filter((attempt) => attempt.metadata.enrollmentId === enrollmentId && this.isAwaitingAward(attempt.result));
|
|
104864
|
+
return pending.toSorted((left, right) => compareNewestCompletedAssessmentAttempt(left.selection, right.selection))[0] ?? null;
|
|
105045
104865
|
}
|
|
105046
|
-
async
|
|
105047
|
-
const
|
|
105048
|
-
|
|
104866
|
+
async pendingAwardSnapshot(listing, context2) {
|
|
104867
|
+
const pending = this.pendingAwardAttempt(listing, context2.enrollment.id);
|
|
104868
|
+
if (!pending) {
|
|
104869
|
+
return null;
|
|
104870
|
+
}
|
|
104871
|
+
addEvent("assessment.pending_award_returned", {
|
|
104872
|
+
"app.assessment.attempt_id": pending.result.sourcedId
|
|
104873
|
+
});
|
|
104874
|
+
return this.snapshotForResult(pending.result, pending.metadata, context2.integration);
|
|
105049
104875
|
}
|
|
105050
|
-
|
|
105051
|
-
|
|
105052
|
-
|
|
105053
|
-
|
|
105054
|
-
|
|
105055
|
-
throw error88;
|
|
104876
|
+
replacedAttemptIds(attempts, enrollmentId) {
|
|
104877
|
+
const replaced = new Set;
|
|
104878
|
+
for (const attempt of attempts.values()) {
|
|
104879
|
+
if (attempt.metadata.enrollmentId === enrollmentId && this.isOpen(attempt.result)) {
|
|
104880
|
+
replaced.add(attempt.result.sourcedId);
|
|
105056
104881
|
}
|
|
105057
|
-
if (shouldConfirmAssessmentVersionUnavailable(error88.details)) {
|
|
105058
|
-
await sleep(TimebackAssessmentRuntimeService.ASSESSMENT_VERSION_CONFIRMATION_DELAY_MS);
|
|
105059
|
-
try {
|
|
105060
|
-
return await resume();
|
|
105061
|
-
} catch (confirmationError) {
|
|
105062
|
-
if (!(confirmationError instanceof AssessmentRuntimeError) || confirmationError.runtimeCode !== "SELECTED_TEST_VERSION_UNAVAILABLE") {
|
|
105063
|
-
throw confirmationError;
|
|
105064
|
-
}
|
|
105065
|
-
}
|
|
105066
|
-
}
|
|
105067
|
-
const staged = attempts.get(decision.attempt.attemptId);
|
|
105068
|
-
pendingSupersessions.add(staged.result.sourcedId);
|
|
105069
|
-
stageAssessmentAttemptSupersession(staged);
|
|
105070
|
-
addEvent("assessment.attempt_supersession_staged", {
|
|
105071
|
-
"app.assessment.attempt_id": decision.attempt.attemptId,
|
|
105072
|
-
"app.assessment.reason": "selected_test_version_unavailable"
|
|
105073
|
-
});
|
|
105074
|
-
return null;
|
|
105075
104882
|
}
|
|
104883
|
+
return replaced;
|
|
105076
104884
|
}
|
|
105077
|
-
|
|
104885
|
+
supersedeReplacedAttempts(db2, attempts, enrollmentId) {
|
|
104886
|
+
return this.supersedeAttemptsBestEffort(db2, this.replacedAttemptIds(attempts, enrollmentId));
|
|
104887
|
+
}
|
|
104888
|
+
async supersedeAttempts(db2, attemptIds) {
|
|
105078
104889
|
for (const attemptId of [...attemptIds].toSorted()) {
|
|
105079
104890
|
await this.deps.assessmentRuntimeLock(db2, [assessmentRuntimeAttemptLockKey(attemptId)], async () => {
|
|
105080
|
-
|
|
105081
|
-
|
|
105082
|
-
result = await this.requireClient().api.oneroster.assessmentResults.get(attemptId);
|
|
105083
|
-
} catch (error88) {
|
|
105084
|
-
if (isApiError(error88) && error88.statusCode === 404) {
|
|
105085
|
-
return;
|
|
105086
|
-
}
|
|
105087
|
-
throw error88;
|
|
105088
|
-
}
|
|
105089
|
-
const update2 = buildAssessmentAttemptSupersessionUpdate(result);
|
|
104891
|
+
const result = await this.findAttemptResult(attemptId);
|
|
104892
|
+
const update2 = result && buildAssessmentAttemptSupersessionUpdate(result);
|
|
105090
104893
|
if (!update2) {
|
|
105091
104894
|
return;
|
|
105092
104895
|
}
|
|
105093
104896
|
await this.requireClient().api.oneroster.assessmentResults.upsert(attemptId, update2);
|
|
105094
104897
|
addEvent("assessment.attempt_superseded", {
|
|
105095
104898
|
"app.assessment.attempt_id": attemptId,
|
|
105096
|
-
"app.assessment.reason": "
|
|
104899
|
+
"app.assessment.reason": "replaced_by_fresh_start"
|
|
105097
104900
|
});
|
|
105098
104901
|
});
|
|
105099
104902
|
}
|
|
105100
104903
|
}
|
|
105101
|
-
async
|
|
104904
|
+
async supersedeAttemptsBestEffort(db2, attemptIds) {
|
|
105102
104905
|
if (attemptIds.size === 0) {
|
|
105103
104906
|
return;
|
|
105104
104907
|
}
|
|
105105
104908
|
try {
|
|
105106
|
-
await this.
|
|
104909
|
+
await this.supersedeAttempts(db2, attemptIds);
|
|
105107
104910
|
} catch (error88) {
|
|
105108
104911
|
addEvent("assessment.attempt_supersession_persist_failed", {
|
|
105109
104912
|
"app.assessment.attempt_ids": [...attemptIds].toSorted().join(","),
|
|
@@ -105112,10 +104915,6 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
105112
104915
|
});
|
|
105113
104916
|
}
|
|
105114
104917
|
}
|
|
105115
|
-
async resumeReviewSnapshot(decision, attempts, context2) {
|
|
105116
|
-
const resumed = attempts.get(decision.attempt.attemptId);
|
|
105117
|
-
return this.snapshotForResult(resumed.result, resumed.metadata, context2.integration);
|
|
105118
|
-
}
|
|
105119
104918
|
recordReviewPreparationPhase(phase, startedAt, counts = {}) {
|
|
105120
104919
|
addEvent("assessment.review_preparation_phase", {
|
|
105121
104920
|
"app.assessment.review_phase": phase,
|
|
@@ -105466,12 +105265,7 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
105466
105265
|
}
|
|
105467
105266
|
});
|
|
105468
105267
|
} catch (error88) {
|
|
105469
|
-
|
|
105470
|
-
throw error88;
|
|
105471
|
-
}
|
|
105472
|
-
addEvent("assessment.line_item_create_conflict", {
|
|
105473
|
-
"app.assessment.line_item_id": lineItemId
|
|
105474
|
-
});
|
|
105268
|
+
await this.confirmLineItemAfterRefusedCreate(lineItemId, error88, "assessment.line_item_create_conflict", () => client2.api.oneroster.assessmentLineItems.get(lineItemId));
|
|
105475
105269
|
}
|
|
105476
105270
|
return lineItemId;
|
|
105477
105271
|
}
|
|
@@ -105513,15 +105307,26 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
105513
105307
|
}
|
|
105514
105308
|
});
|
|
105515
105309
|
} catch (error88) {
|
|
105516
|
-
|
|
105517
|
-
throw error88;
|
|
105518
|
-
}
|
|
105519
|
-
addEvent("assessment.review_bank_line_item_create_conflict", {
|
|
105520
|
-
"app.assessment.line_item_id": lineItemId
|
|
105521
|
-
});
|
|
105310
|
+
await this.confirmLineItemAfterRefusedCreate(lineItemId, error88, "assessment.review_bank_line_item_create_conflict", () => client2.api.oneroster.assessmentLineItems.get(lineItemId));
|
|
105522
105311
|
}
|
|
105523
105312
|
return lineItemId;
|
|
105524
105313
|
}
|
|
105314
|
+
async confirmLineItemAfterRefusedCreate(lineItemId, createError, event, read) {
|
|
105315
|
+
let stored;
|
|
105316
|
+
try {
|
|
105317
|
+
stored = await read();
|
|
105318
|
+
} catch (readError) {
|
|
105319
|
+
if (isApiError(readError) && readError.statusCode === 404) {
|
|
105320
|
+
throw createError;
|
|
105321
|
+
}
|
|
105322
|
+
throw readError;
|
|
105323
|
+
}
|
|
105324
|
+
addEvent(event, {
|
|
105325
|
+
"app.assessment.line_item_id": lineItemId,
|
|
105326
|
+
...isApiError(createError) ? { "app.timeback.status": createError.statusCode } : {}
|
|
105327
|
+
});
|
|
105328
|
+
return stored;
|
|
105329
|
+
}
|
|
105525
105330
|
tokenContext(attempt) {
|
|
105526
105331
|
return {
|
|
105527
105332
|
version: 1,
|
|
@@ -105575,10 +105380,7 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
105575
105380
|
try {
|
|
105576
105381
|
await store.create({ sourcedId: id, title: item.title, ...expected });
|
|
105577
105382
|
} catch (error88) {
|
|
105578
|
-
|
|
105579
|
-
throw error88;
|
|
105580
|
-
}
|
|
105581
|
-
this.assertAssessmentChildLineItem(id, await store.get(id), expected);
|
|
105383
|
+
this.assertAssessmentChildLineItem(id, await this.confirmLineItemAfterRefusedCreate(id, error88, "assessment.question_line_item_create_conflict", () => store.get(id)), expected);
|
|
105582
105384
|
}
|
|
105583
105385
|
}
|
|
105584
105386
|
this.childLineItemCache.set(id, true);
|
|
@@ -105644,14 +105446,8 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
105644
105446
|
...expected
|
|
105645
105447
|
});
|
|
105646
105448
|
} catch (error88) {
|
|
105647
|
-
|
|
105648
|
-
throw error88;
|
|
105649
|
-
}
|
|
105650
|
-
const stored = await client2.api.oneroster.assessmentLineItems.get(lineItemId);
|
|
105449
|
+
const stored = await this.confirmLineItemAfterRefusedCreate(lineItemId, error88, "assessment.diagnostic_question_line_item_create_conflict", () => client2.api.oneroster.assessmentLineItems.get(lineItemId));
|
|
105651
105450
|
this.assertAssessmentChildLineItem(lineItemId, stored, expected);
|
|
105652
|
-
addEvent("assessment.diagnostic_question_line_item_create_conflict", {
|
|
105653
|
-
"app.assessment.line_item_id": lineItemId
|
|
105654
|
-
});
|
|
105655
105451
|
}
|
|
105656
105452
|
return lineItemId;
|
|
105657
105453
|
}
|
|
@@ -105690,14 +105486,8 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
105690
105486
|
...expected
|
|
105691
105487
|
});
|
|
105692
105488
|
} catch (error88) {
|
|
105693
|
-
|
|
105694
|
-
throw error88;
|
|
105695
|
-
}
|
|
105696
|
-
const stored = await client2.api.oneroster.assessmentLineItems.get(lineItemId);
|
|
105489
|
+
const stored = await this.confirmLineItemAfterRefusedCreate(lineItemId, error88, "assessment.review_question_line_item_create_conflict", () => client2.api.oneroster.assessmentLineItems.get(lineItemId));
|
|
105697
105490
|
this.assertAssessmentChildLineItem(lineItemId, stored, expected);
|
|
105698
|
-
addEvent("assessment.review_question_line_item_create_conflict", {
|
|
105699
|
-
"app.assessment.line_item_id": lineItemId
|
|
105700
|
-
});
|
|
105701
105491
|
}
|
|
105702
105492
|
return lineItemId;
|
|
105703
105493
|
}
|
|
@@ -105938,7 +105728,22 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
105938
105728
|
}
|
|
105939
105729
|
return projectReviewAssessment(playable, preparedReviewBankIndex(plan), plan.review.selections);
|
|
105940
105730
|
}
|
|
105941
|
-
async
|
|
105731
|
+
async claimPreparedAttempt(db2, plan, assessment, integration) {
|
|
105732
|
+
const occupant = await this.findAttemptResult(plan.attemptId);
|
|
105733
|
+
if (occupant) {
|
|
105734
|
+
const metadata3 = playcademyAssessmentResultMetadata(occupant.metadata);
|
|
105735
|
+
if (occupant.status !== ONEROSTER_STATUS2.active || !metadata3 || !this.attemptInScope(occupant, metadata3, plan) || metadata3.enrollmentId !== plan.enrollmentId || !this.isOpen(occupant) || !this.attemptMatchesPlan(metadata3, plan)) {
|
|
105736
|
+
return null;
|
|
105737
|
+
}
|
|
105738
|
+
addEvent("assessment.prepared_attempt_recovered", {
|
|
105739
|
+
"app.assessment.attempt_id": plan.attemptId
|
|
105740
|
+
});
|
|
105741
|
+
const [snapshot2] = await Promise.all([
|
|
105742
|
+
this.snapshotForResult(occupant, metadata3, integration),
|
|
105743
|
+
this.supersedeAttemptsBestEffort(db2, new Set(plan.supersedes))
|
|
105744
|
+
]);
|
|
105745
|
+
return snapshot2;
|
|
105746
|
+
}
|
|
105942
105747
|
const timestamp6 = new Date().toISOString();
|
|
105943
105748
|
const metadata2 = buildPreparedAssessmentMetadata(plan, timestamp6, assessment.title);
|
|
105944
105749
|
const result = await this.requireClient().api.oneroster.assessmentResults.upsert(plan.attemptId, {
|
|
@@ -105955,9 +105760,42 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
105955
105760
|
grade: integration.grade
|
|
105956
105761
|
})
|
|
105957
105762
|
});
|
|
105958
|
-
|
|
105959
|
-
|
|
105960
|
-
|
|
105763
|
+
const [snapshot] = await Promise.all([
|
|
105764
|
+
this.isPlatformRoutedDiagnosticMetadata(metadata2) ? this.diagnosticSnapshot(result, metadata2, assessment, integration, {
|
|
105765
|
+
state: metadata2.diagnostic.state
|
|
105766
|
+
}) : this.snapshot(result, metadata2, assessment, integration),
|
|
105767
|
+
this.supersedeAttemptsBestEffort(db2, new Set(plan.supersedes))
|
|
105768
|
+
]);
|
|
105769
|
+
return snapshot;
|
|
105770
|
+
}
|
|
105771
|
+
attemptMatchesPlan(metadata2, plan) {
|
|
105772
|
+
const planned = buildPreparedAssessmentMetadata(plan, metadata2.startedAt, "");
|
|
105773
|
+
if (metadata2.selectedTest.assessmentKey !== planned.selectedTest.assessmentKey || metadata2.selectedTest.identifier !== planned.selectedTest.identifier || metadata2.selectedTest.contentRevision !== planned.selectedTest.contentRevision) {
|
|
105774
|
+
return false;
|
|
105775
|
+
}
|
|
105776
|
+
if (metadata2.purpose === "review" && planned.purpose === "review") {
|
|
105777
|
+
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);
|
|
105778
|
+
}
|
|
105779
|
+
if (metadata2.purpose === "mastery" && planned.purpose === "mastery") {
|
|
105780
|
+
return metadata2.mastery.requestFingerprint === planned.mastery.requestFingerprint;
|
|
105781
|
+
}
|
|
105782
|
+
if (metadata2.purpose === "diagnostic" && plan.purpose === "diagnostic") {
|
|
105783
|
+
return this.isPlatformRoutedDiagnosticMetadata(metadata2) && metadata2.diagnostic.definitionId === plan.diagnostic.definitionId && metadata2.diagnostic.routingRevision === plan.diagnostic.routingRevision;
|
|
105784
|
+
}
|
|
105785
|
+
return metadata2.purpose === planned.purpose;
|
|
105786
|
+
}
|
|
105787
|
+
async findAttemptResult(attemptId) {
|
|
105788
|
+
try {
|
|
105789
|
+
return await this.requireClient().api.oneroster.assessmentResults.get(attemptId);
|
|
105790
|
+
} catch (error88) {
|
|
105791
|
+
if (isApiError(error88) && error88.statusCode === 404) {
|
|
105792
|
+
return null;
|
|
105793
|
+
}
|
|
105794
|
+
throw error88;
|
|
105795
|
+
}
|
|
105796
|
+
}
|
|
105797
|
+
attemptInScope(result, metadata2, scope) {
|
|
105798
|
+
return metadata2.activityId === scope.activityId && metadata2.purpose === scope.purpose && metadata2.courseId === scope.courseId && metadata2.integrationId === scope.integrationId && result.student.sourcedId === scope.studentId;
|
|
105961
105799
|
}
|
|
105962
105800
|
async claimAttemptId(scope, from) {
|
|
105963
105801
|
for (let attemptNumber = from;attemptNumber < from + 20; attemptNumber += 1) {
|
|
@@ -105973,15 +105811,7 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
105973
105811
|
throw new Error(`Assessment attempt numbers ${from} through ${from + 19} are all occupied by records this code cannot read; refusing to overwrite them.`);
|
|
105974
105812
|
}
|
|
105975
105813
|
async attemptIdOccupied(attemptId) {
|
|
105976
|
-
|
|
105977
|
-
await this.requireClient().api.oneroster.assessmentResults.get(attemptId);
|
|
105978
|
-
} catch (error88) {
|
|
105979
|
-
if (isApiError(error88) && error88.statusCode === 404) {
|
|
105980
|
-
return false;
|
|
105981
|
-
}
|
|
105982
|
-
throw error88;
|
|
105983
|
-
}
|
|
105984
|
-
return true;
|
|
105814
|
+
return await this.findAttemptResult(attemptId) !== null;
|
|
105985
105815
|
}
|
|
105986
105816
|
replayCompletion(attempt, submissionId, params, game2) {
|
|
105987
105817
|
const { score, completion } = attempt.metadata;
|