@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/cli.js
CHANGED
|
@@ -1128,7 +1128,7 @@ var package_default;
|
|
|
1128
1128
|
var init_package = __esm(() => {
|
|
1129
1129
|
package_default = {
|
|
1130
1130
|
name: "@playcademy/sandbox",
|
|
1131
|
-
version: "0.8.1-beta.
|
|
1131
|
+
version: "0.8.1-beta.7",
|
|
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
|
}
|
|
@@ -102687,21 +102655,6 @@ function assessmentPreparationFlightKey(input) {
|
|
|
102687
102655
|
async function assessmentPreparationReceiptMatches(plan, request) {
|
|
102688
102656
|
return plan.gameId === request.gameId && plan.studentId === request.studentId && plan.activityId === request.input.activityId && plan.purpose === request.input.purpose && plan.requestHash === await assessmentPreparationRequestHash(request.input);
|
|
102689
102657
|
}
|
|
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
102658
|
function preparedAssessmentIntegration(plan, candidates) {
|
|
102706
102659
|
if (candidates.rows.length !== 1) {
|
|
102707
102660
|
return null;
|
|
@@ -103447,15 +103400,16 @@ function assessmentReviewTranscriptExposures(input) {
|
|
|
103447
103400
|
for (const { result, metadata: metadata2 } of input.attempts) {
|
|
103448
103401
|
const state = { ...result, inProgress: result.inProgress ?? "" };
|
|
103449
103402
|
const settled = isAssessmentAttemptAwaitingAward(state) || isAssessmentAttemptCompleted(state);
|
|
103450
|
-
|
|
103403
|
+
const useTranscript = !settled && metadata2.transcript !== undefined;
|
|
103404
|
+
if (metadata2.purpose === "review" && (useTranscript || settled)) {
|
|
103451
103405
|
authoritative.add(result.sourcedId);
|
|
103452
103406
|
if (metadata2.review.bankRevision === input.bankRevision) {
|
|
103453
|
-
const accepted =
|
|
103454
|
-
itemIdentifier: submission.itemIdentifier,
|
|
103455
|
-
deliveredAt: submission.submittedAt
|
|
103456
|
-
})) : metadata2.transcript.administrations.map((entry) => ({
|
|
103407
|
+
const accepted = useTranscript ? metadata2.transcript.administrations.map((entry) => ({
|
|
103457
103408
|
itemIdentifier: entry.itemIdentifier,
|
|
103458
103409
|
deliveredAt: metadata2.updatedAt
|
|
103410
|
+
})) : metadata2.itemSubmissions.map((submission) => ({
|
|
103411
|
+
itemIdentifier: submission.itemIdentifier,
|
|
103412
|
+
deliveredAt: submission.submittedAt
|
|
103459
103413
|
}));
|
|
103460
103414
|
exposures.push(...accepted);
|
|
103461
103415
|
}
|
|
@@ -103589,7 +103543,6 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
103589
103543
|
static PREPARATION_IN_FLIGHT_LIMIT = 32;
|
|
103590
103544
|
static ASSESSMENT_CACHE_TTL_MS = 60000;
|
|
103591
103545
|
static EXPORT_CONCURRENCY = 4;
|
|
103592
|
-
static ASSESSMENT_VERSION_CONFIRMATION_DELAY_MS = 25;
|
|
103593
103546
|
static SCORING_CONCURRENCY = 4;
|
|
103594
103547
|
static STALE_AWARD_AGE_MS = 15 * 60 * 1000;
|
|
103595
103548
|
deps;
|
|
@@ -103679,8 +103632,6 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
103679
103632
|
return this.startReview({ gameId, studentId, input, user });
|
|
103680
103633
|
}
|
|
103681
103634
|
const masteryStandard = input.purpose === "mastery" ? normalizeMasteryStandard(input.standard) : undefined;
|
|
103682
|
-
const requestFingerprint = this.startRequestFingerprint(input);
|
|
103683
|
-
const pendingSupersessions = new Set;
|
|
103684
103635
|
const candidates = await this.candidateIntegrations(gameId, studentId, input);
|
|
103685
103636
|
const lockKeys = candidates.rows.map((row) => assessmentRuntimeStartLockKey({
|
|
103686
103637
|
studentId,
|
|
@@ -103689,7 +103640,7 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
103689
103640
|
courseId: row.courseId
|
|
103690
103641
|
}));
|
|
103691
103642
|
return this.deps.assessmentRuntimeLock(this.deps.db, lockKeys, async (db2) => {
|
|
103692
|
-
const context2 = await this.selectIntegration(candidates,
|
|
103643
|
+
const context2 = await this.selectIntegration(candidates, input);
|
|
103693
103644
|
const attemptScope = {
|
|
103694
103645
|
studentId,
|
|
103695
103646
|
activityId: input.activityId,
|
|
@@ -103702,39 +103653,28 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
103702
103653
|
this.liveTests(context2.integration.id, input.purpose, db2, masteryStandard),
|
|
103703
103654
|
this.listAttempts(attemptScope)
|
|
103704
103655
|
]);
|
|
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);
|
|
103656
|
+
const pending = await this.pendingAwardSnapshot(listing, context2);
|
|
103657
|
+
if (pending) {
|
|
103658
|
+
return pending;
|
|
103659
|
+
}
|
|
103660
|
+
const decision = this.selectFreshStart(tests, listing, input.purpose);
|
|
103661
|
+
const [assessment, classId, { attemptNumber, attemptId }] = await Promise.all([
|
|
103662
|
+
this.loadAssessment(decision.test.qtiTestIdentifier),
|
|
103663
|
+
this.resolveClassId(context2.integration.courseId, context2.enrollment, studentId),
|
|
103664
|
+
this.claimAttemptId({
|
|
103665
|
+
gameId,
|
|
103666
|
+
studentId,
|
|
103667
|
+
courseId: context2.integration.courseId,
|
|
103668
|
+
activityId: input.activityId,
|
|
103669
|
+
purpose: input.purpose
|
|
103670
|
+
}, nextAssessmentAttemptNumber(listing.attempts.values()))
|
|
103671
|
+
]);
|
|
103725
103672
|
const lineItemId = await this.ensureLineItem({
|
|
103726
103673
|
classId,
|
|
103727
103674
|
integration: context2.integration,
|
|
103728
103675
|
purpose: input.purpose,
|
|
103729
103676
|
assessment
|
|
103730
103677
|
});
|
|
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
103678
|
const timestamp6 = new Date().toISOString();
|
|
103739
103679
|
const metadataBase = {
|
|
103740
103680
|
version: PLAYCADEMY_ASSESSMENT_RESULT_METADATA_VERSION,
|
|
@@ -103759,7 +103699,7 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
103759
103699
|
...metadataBase,
|
|
103760
103700
|
purpose: input.purpose,
|
|
103761
103701
|
mastery: {
|
|
103762
|
-
requestFingerprint,
|
|
103702
|
+
requestFingerprint: masteryRequestFingerprint(input),
|
|
103763
103703
|
standard: masteryStandard
|
|
103764
103704
|
}
|
|
103765
103705
|
} : { ...metadataBase, purpose: input.purpose };
|
|
@@ -103777,8 +103717,11 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
103777
103717
|
grade: context2.integration.grade
|
|
103778
103718
|
})
|
|
103779
103719
|
});
|
|
103780
|
-
await
|
|
103781
|
-
|
|
103720
|
+
const [snapshot] = await Promise.all([
|
|
103721
|
+
this.snapshot(result, metadata2, assessment, context2.integration),
|
|
103722
|
+
this.supersedeReplacedAttempts(db2, listing.attempts, context2.enrollment.id)
|
|
103723
|
+
]);
|
|
103724
|
+
return snapshot;
|
|
103782
103725
|
});
|
|
103783
103726
|
}
|
|
103784
103727
|
async prepareNewAttempt(params, requestHash) {
|
|
@@ -103787,7 +103730,7 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
103787
103730
|
this.candidateIntegrations(params.gameId, params.studentId, params.input),
|
|
103788
103731
|
this.studentAssessmentResults(params.studentId)
|
|
103789
103732
|
]);
|
|
103790
|
-
const context2 = await this.selectIntegration(candidates, params.
|
|
103733
|
+
const context2 = await this.selectIntegration(candidates, params.input);
|
|
103791
103734
|
const attemptScope = {
|
|
103792
103735
|
studentId: params.studentId,
|
|
103793
103736
|
activityId: params.input.activityId,
|
|
@@ -103798,40 +103741,30 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
103798
103741
|
};
|
|
103799
103742
|
const tests = await this.liveTests(context2.integration.id, params.input.purpose, this.deps.db, params.input.purpose === "mastery" ? params.input.standard : undefined, params.input.purpose === "diagnostic" ? params.input.diagnosticKey : undefined);
|
|
103800
103743
|
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"
|
|
103744
|
+
if (this.pendingAwardAttempt(listing, context2.enrollment.id)) {
|
|
103745
|
+
this.recordPreparation("unprepared", params.input.purpose, startedAt, {
|
|
103746
|
+
reason: "pending_award"
|
|
103808
103747
|
});
|
|
103809
|
-
return
|
|
103748
|
+
return { status: "unprepared", reason: "prerequisite_unavailable" };
|
|
103810
103749
|
}
|
|
103811
|
-
if (
|
|
103812
|
-
|
|
103750
|
+
if (candidates.rows.length !== 1) {
|
|
103751
|
+
this.recordPreparation("unprepared", params.input.purpose, startedAt, {
|
|
103752
|
+
reason: "multiple_candidate_courses"
|
|
103753
|
+
});
|
|
103754
|
+
return { status: "unprepared", reason: "prerequisite_unavailable" };
|
|
103813
103755
|
}
|
|
103814
103756
|
const preparation = {
|
|
103815
103757
|
context: context2,
|
|
103816
103758
|
tests,
|
|
103817
103759
|
listing,
|
|
103818
|
-
decision:
|
|
103760
|
+
decision: this.selectFreshStart(tests, listing, params.input.purpose)
|
|
103819
103761
|
};
|
|
103820
103762
|
let prepared;
|
|
103821
103763
|
try {
|
|
103822
103764
|
prepared = await this.buildPreparationPlan(params, requestHash, preparation);
|
|
103823
|
-
|
|
103824
|
-
if (!assessment) {
|
|
103765
|
+
if (!await this.loadPreparedPlanAssessment(prepared.plan)) {
|
|
103825
103766
|
throw new Error("Prepared assessment artifact is unavailable");
|
|
103826
103767
|
}
|
|
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
103768
|
} catch (error88) {
|
|
103836
103769
|
if (error88 instanceof ValidationError || error88 instanceof AssessmentRuntimeError) {
|
|
103837
103770
|
throw error88;
|
|
@@ -103920,6 +103853,7 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
103920
103853
|
courseName: context2.enrollment.course.title,
|
|
103921
103854
|
...attempt,
|
|
103922
103855
|
parentLineItemId,
|
|
103856
|
+
supersedes: [...this.replacedAttemptIds(listing.attempts, context2.enrollment.id)],
|
|
103923
103857
|
selectedTest: {
|
|
103924
103858
|
assessmentKey: decision.test.assessmentKey,
|
|
103925
103859
|
identifier: assessment.identifier,
|
|
@@ -103971,26 +103905,12 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
103971
103905
|
throw AssessmentRuntimeError.from(assessmentReviewBankShortage(selected.fulfillment));
|
|
103972
103906
|
}
|
|
103973
103907
|
const artifact = await this.loadOrCompileReviewBankArtifact(catalog);
|
|
103974
|
-
const assessment = projectReviewAssessment(artifact.assessment, catalog.bank, selected.selections);
|
|
103975
103908
|
const source = { assessment: artifact.assessment, bank: catalog.bank };
|
|
103976
103909
|
const parentLineItemId = await this.provisionPreparationPrerequisites(() => this.ensureReviewBankLineItem({
|
|
103977
103910
|
integration: context2.integration,
|
|
103978
103911
|
activityId: params.input.activityId,
|
|
103979
103912
|
source
|
|
103980
103913
|
}));
|
|
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
103914
|
const attempt = await this.provisionPreparationPrerequisites(() => this.claimAttemptId({
|
|
103995
103915
|
gameId: params.gameId,
|
|
103996
103916
|
studentId: params.studentId,
|
|
@@ -104012,6 +103932,7 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
104012
103932
|
courseName: context2.enrollment.course.title,
|
|
104013
103933
|
...attempt,
|
|
104014
103934
|
parentLineItemId,
|
|
103935
|
+
supersedes: [...this.replacedAttemptIds(listing.attempts, context2.enrollment.id)],
|
|
104015
103936
|
selectedTest: {
|
|
104016
103937
|
assessmentKey: decision.test.assessmentKey,
|
|
104017
103938
|
identifier: artifact.assessment.identifier,
|
|
@@ -104060,10 +103981,7 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
104060
103981
|
if (!integration || !await this.preparedPlanTestIsLive(verified, db2)) {
|
|
104061
103982
|
return null;
|
|
104062
103983
|
}
|
|
104063
|
-
|
|
104064
|
-
return null;
|
|
104065
|
-
}
|
|
104066
|
-
return this.createPreparedAttempt(verified, assessment, integration);
|
|
103984
|
+
return this.claimPreparedAttempt(db2, verified, assessment, integration);
|
|
104067
103985
|
});
|
|
104068
103986
|
if (!outcome) {
|
|
104069
103987
|
this.recordPreparedStart("fallback", params.input.purpose, startedAt, {
|
|
@@ -104088,8 +104006,6 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
104088
104006
|
studentId,
|
|
104089
104007
|
input
|
|
104090
104008
|
}) {
|
|
104091
|
-
const requestFingerprint = this.startRequestFingerprint(input);
|
|
104092
|
-
const pendingSupersessions = new Set;
|
|
104093
104009
|
const candidates = await this.candidateIntegrations(gameId, studentId, input);
|
|
104094
104010
|
const lockKeys = candidates.rows.map((row) => assessmentRuntimeStartLockKey({
|
|
104095
104011
|
studentId,
|
|
@@ -104098,7 +104014,7 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
104098
104014
|
courseId: row.courseId
|
|
104099
104015
|
}));
|
|
104100
104016
|
return this.deps.assessmentRuntimeLock(this.deps.db, lockKeys, async (db2) => {
|
|
104101
|
-
const context2 = await this.selectIntegration(candidates,
|
|
104017
|
+
const context2 = await this.selectIntegration(candidates, input);
|
|
104102
104018
|
const attemptScope = {
|
|
104103
104019
|
studentId,
|
|
104104
104020
|
activityId: input.activityId,
|
|
@@ -104111,32 +104027,25 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
104111
104027
|
this.liveTests(context2.integration.id, input.purpose, db2, undefined, input.diagnosticKey),
|
|
104112
104028
|
this.listAttempts(attemptScope)
|
|
104113
104029
|
]);
|
|
104114
|
-
const
|
|
104115
|
-
if (
|
|
104116
|
-
|
|
104117
|
-
return initialResolution.snapshot;
|
|
104030
|
+
const pending = await this.pendingAwardSnapshot(listing, context2);
|
|
104031
|
+
if (pending) {
|
|
104032
|
+
return pending;
|
|
104118
104033
|
}
|
|
104119
|
-
|
|
104034
|
+
const decision = this.selectFreshStart(tests, listing, input.purpose);
|
|
104120
104035
|
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);
|
|
104036
|
+
const [assessment, classId, { attemptNumber, attemptId }] = await Promise.all([
|
|
104037
|
+
this.loadAssessment(definition.qtiTestIdentifier),
|
|
104038
|
+
this.resolveClassId(context2.integration.courseId, context2.enrollment, studentId),
|
|
104039
|
+
this.claimAttemptId({
|
|
104040
|
+
gameId,
|
|
104041
|
+
studentId,
|
|
104042
|
+
courseId: context2.integration.courseId,
|
|
104043
|
+
activityId: input.activityId,
|
|
104044
|
+
purpose: input.purpose
|
|
104045
|
+
}, nextAssessmentAttemptNumber(listing.attempts.values()))
|
|
104046
|
+
]);
|
|
104047
|
+
definition = await this.requireDiagnosticDefinition(definition.id, context2.integration.id, db2, true);
|
|
104048
|
+
const routing = await this.initializeHostedDiagnostic(definition, assessment);
|
|
104140
104049
|
const lineItemId = await this.ensureLineItem({
|
|
104141
104050
|
classId,
|
|
104142
104051
|
integration: context2.integration,
|
|
@@ -104148,13 +104057,6 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
104148
104057
|
routingRevision: routing.routingRevision
|
|
104149
104058
|
}
|
|
104150
104059
|
});
|
|
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
104060
|
const timestamp6 = new Date().toISOString();
|
|
104159
104061
|
const metadata2 = {
|
|
104160
104062
|
version: PLAYCADEMY_ASSESSMENT_RESULT_METADATA_VERSION,
|
|
@@ -104198,10 +104100,13 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
104198
104100
|
grade: context2.integration.grade
|
|
104199
104101
|
})
|
|
104200
104102
|
});
|
|
104201
|
-
await
|
|
104202
|
-
|
|
104203
|
-
|
|
104204
|
-
|
|
104103
|
+
const [snapshot] = await Promise.all([
|
|
104104
|
+
this.diagnosticSnapshot(result, metadata2, assessment, context2.integration, {
|
|
104105
|
+
state: routing.state
|
|
104106
|
+
}),
|
|
104107
|
+
this.supersedeReplacedAttempts(db2, listing.attempts, context2.enrollment.id)
|
|
104108
|
+
]);
|
|
104109
|
+
return snapshot;
|
|
104205
104110
|
});
|
|
104206
104111
|
}
|
|
104207
104112
|
async startReview({
|
|
@@ -104211,7 +104116,6 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
104211
104116
|
}) {
|
|
104212
104117
|
const preparationStartedAt = Date.now();
|
|
104213
104118
|
const requestFingerprint = reviewRequestFingerprint(input, DEFAULT_REVIEW_SELECTION_POLICY);
|
|
104214
|
-
const pendingSupersessions = new Set;
|
|
104215
104119
|
const normalizedRequest = normalizeReviewRequest(input);
|
|
104216
104120
|
const candidates = await this.candidateIntegrations(gameId, studentId, input);
|
|
104217
104121
|
const lockKeys = candidates.rows.map((row) => assessmentRuntimeStartLockKey({
|
|
@@ -104221,7 +104125,7 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
104221
104125
|
courseId: row.courseId
|
|
104222
104126
|
}));
|
|
104223
104127
|
return this.deps.assessmentRuntimeLock(this.deps.db, lockKeys, async (db2) => {
|
|
104224
|
-
const context2 = await this.selectIntegration(candidates,
|
|
104128
|
+
const context2 = await this.selectIntegration(candidates, input);
|
|
104225
104129
|
const attemptScope = {
|
|
104226
104130
|
studentId,
|
|
104227
104131
|
activityId: input.activityId,
|
|
@@ -104234,42 +104138,22 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
104234
104138
|
this.liveTests(context2.integration.id, input.purpose, db2),
|
|
104235
104139
|
this.listAttempts(attemptScope)
|
|
104236
104140
|
]);
|
|
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));
|
|
104141
|
+
const pending = await this.pendingAwardSnapshot(listing, context2);
|
|
104142
|
+
if (pending) {
|
|
104143
|
+
return pending;
|
|
104246
104144
|
}
|
|
104145
|
+
const decision = this.selectFreshStart(tests, listing, input.purpose);
|
|
104247
104146
|
if (tests.length > 1) {
|
|
104248
104147
|
throw new ValidationError("Standards review requires exactly one live review-bank assessment.");
|
|
104249
104148
|
}
|
|
104250
104149
|
const manifestStartedAt = Date.now();
|
|
104251
|
-
|
|
104150
|
+
const catalog = await this.loadReviewBankCatalog(decision.test.qtiTestIdentifier);
|
|
104252
104151
|
this.recordReviewPreparationPhase("manifest_load", manifestStartedAt, {
|
|
104253
104152
|
candidateCount: catalog.bank.items.length
|
|
104254
104153
|
});
|
|
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
104154
|
const exposures = assessmentReviewTranscriptExposures({
|
|
104271
|
-
attempts:
|
|
104272
|
-
children:
|
|
104155
|
+
attempts: listing.attempts.values(),
|
|
104156
|
+
children: listing.reviewChildren.values(),
|
|
104273
104157
|
bankRevision: catalog.bank.bankRevision
|
|
104274
104158
|
});
|
|
104275
104159
|
const selected = selectReviewItems({
|
|
@@ -104290,18 +104174,20 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
104290
104174
|
});
|
|
104291
104175
|
const assessment = projectReviewAssessment(source.assessment, source.bank, selected.selections);
|
|
104292
104176
|
const provisioningStartedAt = Date.now();
|
|
104293
|
-
const parentLineItemId = await
|
|
104294
|
-
|
|
104295
|
-
|
|
104296
|
-
|
|
104297
|
-
|
|
104298
|
-
|
|
104299
|
-
|
|
104300
|
-
|
|
104301
|
-
|
|
104302
|
-
|
|
104303
|
-
|
|
104304
|
-
|
|
104177
|
+
const [parentLineItemId, { attemptNumber, attemptId }] = await Promise.all([
|
|
104178
|
+
this.ensureReviewBankLineItem({
|
|
104179
|
+
integration: context2.integration,
|
|
104180
|
+
activityId: input.activityId,
|
|
104181
|
+
source
|
|
104182
|
+
}),
|
|
104183
|
+
this.claimAttemptId({
|
|
104184
|
+
gameId,
|
|
104185
|
+
studentId,
|
|
104186
|
+
courseId: context2.integration.courseId,
|
|
104187
|
+
activityId: input.activityId,
|
|
104188
|
+
purpose: input.purpose
|
|
104189
|
+
}, nextAssessmentAttemptNumber(listing.attempts.values()))
|
|
104190
|
+
]);
|
|
104305
104191
|
const timestamp6 = new Date().toISOString();
|
|
104306
104192
|
const metadata2 = {
|
|
104307
104193
|
version: PLAYCADEMY_ASSESSMENT_RESULT_METADATA_VERSION,
|
|
@@ -104345,13 +104231,16 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
104345
104231
|
grade: context2.integration.grade
|
|
104346
104232
|
})
|
|
104347
104233
|
});
|
|
104348
|
-
await
|
|
104234
|
+
const [snapshot] = await Promise.all([
|
|
104235
|
+
this.snapshot(result, metadata2, assessment, context2.integration),
|
|
104236
|
+
this.supersedeReplacedAttempts(db2, listing.attempts, context2.enrollment.id)
|
|
104237
|
+
]);
|
|
104349
104238
|
this.recordReviewPreparationPhase("provisioning", provisioningStartedAt);
|
|
104350
104239
|
this.recordReviewPreparationPhase("total", preparationStartedAt, {
|
|
104351
104240
|
candidateCount: catalog.bank.items.length,
|
|
104352
104241
|
selectedCount: selected.selections.length
|
|
104353
104242
|
});
|
|
104354
|
-
return
|
|
104243
|
+
return snapshot;
|
|
104355
104244
|
});
|
|
104356
104245
|
}
|
|
104357
104246
|
async get(params) {
|
|
@@ -104869,28 +104758,11 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
104869
104758
|
liveTestIntegrationIds: integrationIdsWithLiveTests
|
|
104870
104759
|
};
|
|
104871
104760
|
}
|
|
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
|
-
}
|
|
104761
|
+
async selectIntegration(candidates, input) {
|
|
104762
|
+
const { rows: candidateRows, enrollmentByCourse, liveTestIntegrationIds } = candidates;
|
|
104890
104763
|
const annotated = candidateRows.map((row) => ({
|
|
104891
104764
|
...row,
|
|
104892
|
-
hasLiveTests:
|
|
104893
|
-
hasOpenAttempt: openAttemptCourseIds.has(row.courseId)
|
|
104765
|
+
hasLiveTests: liveTestIntegrationIds.has(row.id)
|
|
104894
104766
|
}));
|
|
104895
104767
|
const selection = selectAssessmentCourseCandidate(annotated, Boolean(input.subject));
|
|
104896
104768
|
if (selection.outcome === "none") {
|
|
@@ -104919,9 +104791,6 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
104919
104791
|
standard: assessmentStandardForRow(row)
|
|
104920
104792
|
})).filter((test) => !matchesMasteryStandard || matchesMasteryStandard(test.standard));
|
|
104921
104793
|
}
|
|
104922
|
-
async openAttemptCourseIds(scope) {
|
|
104923
|
-
return this.openAttemptCourseIdsFromResults(await this.studentAssessmentResults(scope.studentId), scope);
|
|
104924
|
-
}
|
|
104925
104794
|
async studentAssessmentResults(studentId) {
|
|
104926
104795
|
const stream = this.requireClient().api.oneroster.assessmentResults.stream({
|
|
104927
104796
|
where: {
|
|
@@ -104935,16 +104804,6 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
104935
104804
|
}
|
|
104936
104805
|
return results;
|
|
104937
104806
|
}
|
|
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
104807
|
async listAttempts(scope) {
|
|
104949
104808
|
return this.attemptListing(await this.studentAssessmentResults(scope.studentId), scope);
|
|
104950
104809
|
}
|
|
@@ -104975,7 +104834,7 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
104975
104834
|
}
|
|
104976
104835
|
}
|
|
104977
104836
|
}
|
|
104978
|
-
if (metadata2 &&
|
|
104837
|
+
if (metadata2 && this.attemptInScope(result, metadata2, scope)) {
|
|
104979
104838
|
const selectedAssessmentKey = metadata2.selectedTest.assessmentKey ?? assessmentKeyFromManagedQtiIdentifier(metadata2.selectedTest.identifier);
|
|
104980
104839
|
if (selectedAssessmentKey) {
|
|
104981
104840
|
attempts.set(result.sourcedId, {
|
|
@@ -104984,12 +104843,9 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
104984
104843
|
selection: {
|
|
104985
104844
|
attemptId: result.sourcedId,
|
|
104986
104845
|
selectedAssessmentKey,
|
|
104987
|
-
selectedTestIdentifier: metadata2.selectedTest.identifier,
|
|
104988
104846
|
inProgress: result.inProgress ?? "",
|
|
104989
104847
|
scoreStatus: result.scoreStatus,
|
|
104990
|
-
scoreDate: result.scoreDate
|
|
104991
|
-
updatedAt: result.dateLastModified ?? metadata2.updatedAt,
|
|
104992
|
-
resumable: metadata2.enrollmentId === scope.enrollmentId
|
|
104848
|
+
scoreDate: result.scoreDate
|
|
104993
104849
|
}
|
|
104994
104850
|
});
|
|
104995
104851
|
}
|
|
@@ -104997,114 +104853,61 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
104997
104853
|
}
|
|
104998
104854
|
return { attempts, reviewChildren };
|
|
104999
104855
|
}
|
|
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) {
|
|
104856
|
+
selectFreshStart(tests, listing, purpose) {
|
|
104857
|
+
const decision = selectRuntimeAssessment(tests, [...listing.attempts.values()].map((entry) => entry.selection));
|
|
105028
104858
|
if (!decision) {
|
|
105029
104859
|
throw AssessmentRuntimeError.from(assessmentNoEligibleTests(purpose));
|
|
105030
104860
|
}
|
|
105031
104861
|
return decision;
|
|
105032
104862
|
}
|
|
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);
|
|
104863
|
+
pendingAwardAttempt(listing, enrollmentId) {
|
|
104864
|
+
const pending = [...listing.attempts.values()].filter((attempt) => attempt.metadata.enrollmentId === enrollmentId && this.isAwaitingAward(attempt.result));
|
|
104865
|
+
return pending.toSorted((left, right) => compareNewestCompletedAssessmentAttempt(left.selection, right.selection))[0] ?? null;
|
|
105046
104866
|
}
|
|
105047
|
-
async
|
|
105048
|
-
const
|
|
105049
|
-
|
|
104867
|
+
async pendingAwardSnapshot(listing, context2) {
|
|
104868
|
+
const pending = this.pendingAwardAttempt(listing, context2.enrollment.id);
|
|
104869
|
+
if (!pending) {
|
|
104870
|
+
return null;
|
|
104871
|
+
}
|
|
104872
|
+
addEvent("assessment.pending_award_returned", {
|
|
104873
|
+
"app.assessment.attempt_id": pending.result.sourcedId
|
|
104874
|
+
});
|
|
104875
|
+
return this.snapshotForResult(pending.result, pending.metadata, context2.integration);
|
|
105050
104876
|
}
|
|
105051
|
-
|
|
105052
|
-
|
|
105053
|
-
|
|
105054
|
-
|
|
105055
|
-
|
|
105056
|
-
throw error88;
|
|
104877
|
+
replacedAttemptIds(attempts, enrollmentId) {
|
|
104878
|
+
const replaced = new Set;
|
|
104879
|
+
for (const attempt of attempts.values()) {
|
|
104880
|
+
if (attempt.metadata.enrollmentId === enrollmentId && this.isOpen(attempt.result)) {
|
|
104881
|
+
replaced.add(attempt.result.sourcedId);
|
|
105057
104882
|
}
|
|
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
|
-
}
|
|
105067
|
-
}
|
|
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
104883
|
}
|
|
104884
|
+
return replaced;
|
|
105077
104885
|
}
|
|
105078
|
-
|
|
104886
|
+
supersedeReplacedAttempts(db2, attempts, enrollmentId) {
|
|
104887
|
+
return this.supersedeAttemptsBestEffort(db2, this.replacedAttemptIds(attempts, enrollmentId));
|
|
104888
|
+
}
|
|
104889
|
+
async supersedeAttempts(db2, attemptIds) {
|
|
105079
104890
|
for (const attemptId of [...attemptIds].toSorted()) {
|
|
105080
104891
|
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);
|
|
104892
|
+
const result = await this.findAttemptResult(attemptId);
|
|
104893
|
+
const update2 = result && buildAssessmentAttemptSupersessionUpdate(result);
|
|
105091
104894
|
if (!update2) {
|
|
105092
104895
|
return;
|
|
105093
104896
|
}
|
|
105094
104897
|
await this.requireClient().api.oneroster.assessmentResults.upsert(attemptId, update2);
|
|
105095
104898
|
addEvent("assessment.attempt_superseded", {
|
|
105096
104899
|
"app.assessment.attempt_id": attemptId,
|
|
105097
|
-
"app.assessment.reason": "
|
|
104900
|
+
"app.assessment.reason": "replaced_by_fresh_start"
|
|
105098
104901
|
});
|
|
105099
104902
|
});
|
|
105100
104903
|
}
|
|
105101
104904
|
}
|
|
105102
|
-
async
|
|
104905
|
+
async supersedeAttemptsBestEffort(db2, attemptIds) {
|
|
105103
104906
|
if (attemptIds.size === 0) {
|
|
105104
104907
|
return;
|
|
105105
104908
|
}
|
|
105106
104909
|
try {
|
|
105107
|
-
await this.
|
|
104910
|
+
await this.supersedeAttempts(db2, attemptIds);
|
|
105108
104911
|
} catch (error88) {
|
|
105109
104912
|
addEvent("assessment.attempt_supersession_persist_failed", {
|
|
105110
104913
|
"app.assessment.attempt_ids": [...attemptIds].toSorted().join(","),
|
|
@@ -105113,10 +104916,6 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
105113
104916
|
});
|
|
105114
104917
|
}
|
|
105115
104918
|
}
|
|
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
104919
|
recordReviewPreparationPhase(phase, startedAt, counts = {}) {
|
|
105121
104920
|
addEvent("assessment.review_preparation_phase", {
|
|
105122
104921
|
"app.assessment.review_phase": phase,
|
|
@@ -105467,12 +105266,7 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
105467
105266
|
}
|
|
105468
105267
|
});
|
|
105469
105268
|
} catch (error88) {
|
|
105470
|
-
|
|
105471
|
-
throw error88;
|
|
105472
|
-
}
|
|
105473
|
-
addEvent("assessment.line_item_create_conflict", {
|
|
105474
|
-
"app.assessment.line_item_id": lineItemId
|
|
105475
|
-
});
|
|
105269
|
+
await this.confirmLineItemAfterRefusedCreate(lineItemId, error88, "assessment.line_item_create_conflict", () => client2.api.oneroster.assessmentLineItems.get(lineItemId));
|
|
105476
105270
|
}
|
|
105477
105271
|
return lineItemId;
|
|
105478
105272
|
}
|
|
@@ -105514,15 +105308,26 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
105514
105308
|
}
|
|
105515
105309
|
});
|
|
105516
105310
|
} catch (error88) {
|
|
105517
|
-
|
|
105518
|
-
throw error88;
|
|
105519
|
-
}
|
|
105520
|
-
addEvent("assessment.review_bank_line_item_create_conflict", {
|
|
105521
|
-
"app.assessment.line_item_id": lineItemId
|
|
105522
|
-
});
|
|
105311
|
+
await this.confirmLineItemAfterRefusedCreate(lineItemId, error88, "assessment.review_bank_line_item_create_conflict", () => client2.api.oneroster.assessmentLineItems.get(lineItemId));
|
|
105523
105312
|
}
|
|
105524
105313
|
return lineItemId;
|
|
105525
105314
|
}
|
|
105315
|
+
async confirmLineItemAfterRefusedCreate(lineItemId, createError, event, read) {
|
|
105316
|
+
let stored;
|
|
105317
|
+
try {
|
|
105318
|
+
stored = await read();
|
|
105319
|
+
} catch (readError) {
|
|
105320
|
+
if (isApiError(readError) && readError.statusCode === 404) {
|
|
105321
|
+
throw createError;
|
|
105322
|
+
}
|
|
105323
|
+
throw readError;
|
|
105324
|
+
}
|
|
105325
|
+
addEvent(event, {
|
|
105326
|
+
"app.assessment.line_item_id": lineItemId,
|
|
105327
|
+
...isApiError(createError) ? { "app.timeback.status": createError.statusCode } : {}
|
|
105328
|
+
});
|
|
105329
|
+
return stored;
|
|
105330
|
+
}
|
|
105526
105331
|
tokenContext(attempt) {
|
|
105527
105332
|
return {
|
|
105528
105333
|
version: 1,
|
|
@@ -105576,10 +105381,7 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
105576
105381
|
try {
|
|
105577
105382
|
await store.create({ sourcedId: id, title: item.title, ...expected });
|
|
105578
105383
|
} catch (error88) {
|
|
105579
|
-
|
|
105580
|
-
throw error88;
|
|
105581
|
-
}
|
|
105582
|
-
this.assertAssessmentChildLineItem(id, await store.get(id), expected);
|
|
105384
|
+
this.assertAssessmentChildLineItem(id, await this.confirmLineItemAfterRefusedCreate(id, error88, "assessment.question_line_item_create_conflict", () => store.get(id)), expected);
|
|
105583
105385
|
}
|
|
105584
105386
|
}
|
|
105585
105387
|
this.childLineItemCache.set(id, true);
|
|
@@ -105645,14 +105447,8 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
105645
105447
|
...expected
|
|
105646
105448
|
});
|
|
105647
105449
|
} catch (error88) {
|
|
105648
|
-
|
|
105649
|
-
throw error88;
|
|
105650
|
-
}
|
|
105651
|
-
const stored = await client2.api.oneroster.assessmentLineItems.get(lineItemId);
|
|
105450
|
+
const stored = await this.confirmLineItemAfterRefusedCreate(lineItemId, error88, "assessment.diagnostic_question_line_item_create_conflict", () => client2.api.oneroster.assessmentLineItems.get(lineItemId));
|
|
105652
105451
|
this.assertAssessmentChildLineItem(lineItemId, stored, expected);
|
|
105653
|
-
addEvent("assessment.diagnostic_question_line_item_create_conflict", {
|
|
105654
|
-
"app.assessment.line_item_id": lineItemId
|
|
105655
|
-
});
|
|
105656
105452
|
}
|
|
105657
105453
|
return lineItemId;
|
|
105658
105454
|
}
|
|
@@ -105691,14 +105487,8 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
105691
105487
|
...expected
|
|
105692
105488
|
});
|
|
105693
105489
|
} catch (error88) {
|
|
105694
|
-
|
|
105695
|
-
throw error88;
|
|
105696
|
-
}
|
|
105697
|
-
const stored = await client2.api.oneroster.assessmentLineItems.get(lineItemId);
|
|
105490
|
+
const stored = await this.confirmLineItemAfterRefusedCreate(lineItemId, error88, "assessment.review_question_line_item_create_conflict", () => client2.api.oneroster.assessmentLineItems.get(lineItemId));
|
|
105698
105491
|
this.assertAssessmentChildLineItem(lineItemId, stored, expected);
|
|
105699
|
-
addEvent("assessment.review_question_line_item_create_conflict", {
|
|
105700
|
-
"app.assessment.line_item_id": lineItemId
|
|
105701
|
-
});
|
|
105702
105492
|
}
|
|
105703
105493
|
return lineItemId;
|
|
105704
105494
|
}
|
|
@@ -105939,7 +105729,22 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
105939
105729
|
}
|
|
105940
105730
|
return projectReviewAssessment(playable, preparedReviewBankIndex(plan), plan.review.selections);
|
|
105941
105731
|
}
|
|
105942
|
-
async
|
|
105732
|
+
async claimPreparedAttempt(db2, plan, assessment, integration) {
|
|
105733
|
+
const occupant = await this.findAttemptResult(plan.attemptId);
|
|
105734
|
+
if (occupant) {
|
|
105735
|
+
const metadata3 = playcademyAssessmentResultMetadata(occupant.metadata);
|
|
105736
|
+
if (occupant.status !== ONEROSTER_STATUS2.active || !metadata3 || !this.attemptInScope(occupant, metadata3, plan) || metadata3.enrollmentId !== plan.enrollmentId || !this.isOpen(occupant) || !this.attemptMatchesPlan(metadata3, plan)) {
|
|
105737
|
+
return null;
|
|
105738
|
+
}
|
|
105739
|
+
addEvent("assessment.prepared_attempt_recovered", {
|
|
105740
|
+
"app.assessment.attempt_id": plan.attemptId
|
|
105741
|
+
});
|
|
105742
|
+
const [snapshot2] = await Promise.all([
|
|
105743
|
+
this.snapshotForResult(occupant, metadata3, integration),
|
|
105744
|
+
this.supersedeAttemptsBestEffort(db2, new Set(plan.supersedes))
|
|
105745
|
+
]);
|
|
105746
|
+
return snapshot2;
|
|
105747
|
+
}
|
|
105943
105748
|
const timestamp6 = new Date().toISOString();
|
|
105944
105749
|
const metadata2 = buildPreparedAssessmentMetadata(plan, timestamp6, assessment.title);
|
|
105945
105750
|
const result = await this.requireClient().api.oneroster.assessmentResults.upsert(plan.attemptId, {
|
|
@@ -105956,9 +105761,42 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
105956
105761
|
grade: integration.grade
|
|
105957
105762
|
})
|
|
105958
105763
|
});
|
|
105959
|
-
|
|
105960
|
-
|
|
105961
|
-
|
|
105764
|
+
const [snapshot] = await Promise.all([
|
|
105765
|
+
this.isPlatformRoutedDiagnosticMetadata(metadata2) ? this.diagnosticSnapshot(result, metadata2, assessment, integration, {
|
|
105766
|
+
state: metadata2.diagnostic.state
|
|
105767
|
+
}) : this.snapshot(result, metadata2, assessment, integration),
|
|
105768
|
+
this.supersedeAttemptsBestEffort(db2, new Set(plan.supersedes))
|
|
105769
|
+
]);
|
|
105770
|
+
return snapshot;
|
|
105771
|
+
}
|
|
105772
|
+
attemptMatchesPlan(metadata2, plan) {
|
|
105773
|
+
const planned = buildPreparedAssessmentMetadata(plan, metadata2.startedAt, "");
|
|
105774
|
+
if (metadata2.selectedTest.assessmentKey !== planned.selectedTest.assessmentKey || metadata2.selectedTest.identifier !== planned.selectedTest.identifier || metadata2.selectedTest.contentRevision !== planned.selectedTest.contentRevision) {
|
|
105775
|
+
return false;
|
|
105776
|
+
}
|
|
105777
|
+
if (metadata2.purpose === "review" && planned.purpose === "review") {
|
|
105778
|
+
return metadata2.review.requestFingerprint === planned.review.requestFingerprint && metadata2.review.bankRevision === planned.review.bankRevision && metadata2.review.selections.length === planned.review.selections.length && metadata2.review.selections.every((selection, index2) => selection.itemIdentifier === planned.review.selections[index2].itemIdentifier);
|
|
105779
|
+
}
|
|
105780
|
+
if (metadata2.purpose === "mastery" && planned.purpose === "mastery") {
|
|
105781
|
+
return metadata2.mastery.requestFingerprint === planned.mastery.requestFingerprint;
|
|
105782
|
+
}
|
|
105783
|
+
if (metadata2.purpose === "diagnostic" && plan.purpose === "diagnostic") {
|
|
105784
|
+
return this.isPlatformRoutedDiagnosticMetadata(metadata2) && metadata2.diagnostic.definitionId === plan.diagnostic.definitionId && metadata2.diagnostic.routingRevision === plan.diagnostic.routingRevision;
|
|
105785
|
+
}
|
|
105786
|
+
return metadata2.purpose === planned.purpose;
|
|
105787
|
+
}
|
|
105788
|
+
async findAttemptResult(attemptId) {
|
|
105789
|
+
try {
|
|
105790
|
+
return await this.requireClient().api.oneroster.assessmentResults.get(attemptId);
|
|
105791
|
+
} catch (error88) {
|
|
105792
|
+
if (isApiError(error88) && error88.statusCode === 404) {
|
|
105793
|
+
return null;
|
|
105794
|
+
}
|
|
105795
|
+
throw error88;
|
|
105796
|
+
}
|
|
105797
|
+
}
|
|
105798
|
+
attemptInScope(result, metadata2, scope) {
|
|
105799
|
+
return metadata2.activityId === scope.activityId && metadata2.purpose === scope.purpose && metadata2.courseId === scope.courseId && metadata2.integrationId === scope.integrationId && result.student.sourcedId === scope.studentId;
|
|
105962
105800
|
}
|
|
105963
105801
|
async claimAttemptId(scope, from) {
|
|
105964
105802
|
for (let attemptNumber = from;attemptNumber < from + 20; attemptNumber += 1) {
|
|
@@ -105974,15 +105812,7 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
105974
105812
|
throw new Error(`Assessment attempt numbers ${from} through ${from + 19} are all occupied by records this code cannot read; refusing to overwrite them.`);
|
|
105975
105813
|
}
|
|
105976
105814
|
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;
|
|
105815
|
+
return await this.findAttemptResult(attemptId) !== null;
|
|
105986
105816
|
}
|
|
105987
105817
|
replayCompletion(attempt, submissionId, params, game2) {
|
|
105988
105818
|
const { score, completion } = attempt.metadata;
|