@playcademy/sandbox 0.8.1-beta.5 → 0.8.1-beta.6

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/server.js CHANGED
@@ -284,7 +284,7 @@ var init_platform = __esm(() => {
284
284
  var PLATFORM_TIMEZONE = "America/New_York";
285
285
 
286
286
  // ../constants/src/timeback.ts
287
- var TIMEBACK_GRADES, TIMEBACK_SUBJECTS, ASSESSMENT_PURPOSES, PLAYCADEMY_ASSESSMENT_RESULT_METADATA_VERSION = 1, PLAYCADEMY_ASSESSMENT_ITEM_RESULT_METADATA_VERSION = 1, PLAYCADEMY_ASSESSMENT_CONTRACT_VERSION = 1, PLAYCADEMY_ASSESSMENT_RESULT_METADATA_KEY = "playcademyAssessment", PLAYCADEMY_ASSESSMENT_ITEM_RESULT_METADATA_KEY = "playcademyAssessmentItem", TIMEBACK_ASSESSMENT_REVIEW_REQUEST_LIMITS, TIMEBACK_ORG_SOURCED_ID = "PLAYCADEMY", TIMEBACK_ORG_NAME = "Playcademy Studios", TIMEBACK_ORG_TYPE = "department", TIMEBACK_COURSE_DEFAULTS, TIMEBACK_RESOURCE_DEFAULTS, TIMEBACK_COMPONENT_DEFAULTS, TIMEBACK_COMPONENT_RESOURCE_DEFAULTS, TIMEBACK_ASSESSMENT_RESPONSE_VALUE_MAX_LENGTH = 2048, TIMEBACK_GAME_METRIC_DECIMAL_PLACES, TIMEBACK_GAME_METRIC_COMPARISON_TOLERANCE;
287
+ var TIMEBACK_GRADES, TIMEBACK_SUBJECTS, ASSESSMENT_PURPOSES, PLAYCADEMY_ASSESSMENT_RESULT_METADATA_VERSION = 1, PLAYCADEMY_ASSESSMENT_CONTRACT_VERSION = 1, PLAYCADEMY_ASSESSMENT_RESULT_METADATA_KEY = "playcademyAssessment", PLAYCADEMY_ASSESSMENT_ITEM_RESULT_METADATA_KEY = "playcademyAssessmentItem", TIMEBACK_ASSESSMENT_REVIEW_REQUEST_LIMITS, TIMEBACK_ORG_SOURCED_ID = "PLAYCADEMY", TIMEBACK_ORG_NAME = "Playcademy Studios", TIMEBACK_ORG_TYPE = "department", TIMEBACK_COURSE_DEFAULTS, TIMEBACK_RESOURCE_DEFAULTS, TIMEBACK_COMPONENT_DEFAULTS, TIMEBACK_COMPONENT_RESOURCE_DEFAULTS, TIMEBACK_ASSESSMENT_RESPONSE_VALUE_MAX_LENGTH = 2048, TIMEBACK_GAME_METRIC_DECIMAL_PLACES, TIMEBACK_GAME_METRIC_COMPARISON_TOLERANCE;
288
288
  var init_timeback2 = __esm(() => {
289
289
  TIMEBACK_GRADES = [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13];
290
290
  TIMEBACK_SUBJECTS = [
@@ -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.5",
1130
+ version: "0.8.1-beta.6",
1131
1131
  description: "Local development server for Playcademy game development",
1132
1132
  type: "module",
1133
1133
  exports: {
@@ -9239,26 +9239,6 @@ var init_v3 = __esm(() => {
9239
9239
  var init_esm = __esm(() => {
9240
9240
  init_v3();
9241
9241
  });
9242
- // ../types/src/timeback/assessment-runtime.ts
9243
- var PLAYABLE_ASSESSMENT_SHAPES;
9244
- var init_assessment_runtime = __esm(() => {
9245
- PLAYABLE_ASSESSMENT_SHAPES = ["circle", "ellipse", "rect", "poly", "default"];
9246
- });
9247
- // ../types/src/timeback/diagnostic-routing.ts
9248
- var DIAGNOSTIC_ROUTING_MANIFEST_VERSION = 1, DIAGNOSTIC_ROUTING_STATE_VERSION = 1, DIAGNOSTIC_TRACK_OUTCOMES;
9249
- var init_diagnostic_routing = __esm(() => {
9250
- DIAGNOSTIC_TRACK_OUTCOMES = [
9251
- "provisional",
9252
- "unresolved",
9253
- "route-to-instruction",
9254
- "not-assessed"
9255
- ];
9256
- });
9257
- // ../types/src/timeback/index.ts
9258
- var init_timeback4 = __esm(() => {
9259
- init_assessment_runtime();
9260
- init_diagnostic_routing();
9261
- });
9262
9242
 
9263
9243
  // ../utils/src/uuid.ts
9264
9244
  function isValidUUID(value) {
@@ -9282,6 +9262,26 @@ var UUID_REGEX;
9282
9262
  var init_uuid = __esm(() => {
9283
9263
  UUID_REGEX = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
9284
9264
  });
9265
+ // ../types/src/timeback/assessment-runtime.ts
9266
+ var PLAYABLE_ASSESSMENT_SHAPES;
9267
+ var init_assessment_runtime = __esm(() => {
9268
+ PLAYABLE_ASSESSMENT_SHAPES = ["circle", "ellipse", "rect", "poly", "default"];
9269
+ });
9270
+ // ../types/src/timeback/diagnostic-routing.ts
9271
+ var DIAGNOSTIC_ROUTING_MANIFEST_VERSION = 1, DIAGNOSTIC_ROUTING_STATE_VERSION = 1, DIAGNOSTIC_TRACK_OUTCOMES;
9272
+ var init_diagnostic_routing = __esm(() => {
9273
+ DIAGNOSTIC_TRACK_OUTCOMES = [
9274
+ "provisional",
9275
+ "unresolved",
9276
+ "route-to-instruction",
9277
+ "not-assessed"
9278
+ ];
9279
+ });
9280
+ // ../types/src/timeback/index.ts
9281
+ var init_timeback4 = __esm(() => {
9282
+ init_assessment_runtime();
9283
+ init_diagnostic_routing();
9284
+ });
9285
9285
 
9286
9286
  // ../timeback/dist/engines/assessment-runtime/index.js
9287
9287
  function qtiDirectedPairValidationIssue(values, contract) {
@@ -9561,6 +9561,25 @@ function assessmentReviewBankShortage(fulfillment) {
9561
9561
  function assessmentFlowForPurpose(purpose) {
9562
9562
  return purpose === "review" ? "item-submit" : "attempt-submit";
9563
9563
  }
9564
+ function isObject(value) {
9565
+ return typeof value === "object" && value !== null;
9566
+ }
9567
+ function isTimebackGrade(value) {
9568
+ return typeof value === "number" && Number.isInteger(value) && GRADE_VALUES.includes(value);
9569
+ }
9570
+ function isAssessmentSessionTiming(value) {
9571
+ return isObject(value) && typeof value.runId === "string" && isValidUUID(value.runId) && typeof value.resumeId === "string" && isValidUUID(value.resumeId) && isNonNegativeDuration(value.activeSeconds) && (value.inactiveSeconds === undefined || isNonNegativeDuration(value.inactiveSeconds));
9572
+ }
9573
+ function isNonNegativeDuration(value) {
9574
+ return typeof value === "number" && Number.isFinite(value) && value >= 0;
9575
+ }
9576
+ function isAssessmentPurpose(value) {
9577
+ return ASSESSMENT_PURPOSES.includes(value);
9578
+ }
9579
+ function isAssessmentResponseValue(value) {
9580
+ const entries = Array.isArray(value) ? value : [value];
9581
+ return entries.length > 0 && entries.every((entry) => typeof entry === "string" && entry.length > 0 && entry.length <= TIMEBACK_ASSESSMENT_RESPONSE_VALUE_MAX_LENGTH);
9582
+ }
9564
9583
  function compareCodeUnits(left, right) {
9565
9584
  if (left < right) {
9566
9585
  return -1;
@@ -10414,48 +10433,6 @@ function projectDiagnosticRoutingSnapshot(revision, state) {
10414
10433
  }))
10415
10434
  };
10416
10435
  }
10417
- function isObject(value) {
10418
- return typeof value === "object" && value !== null;
10419
- }
10420
- function isTimebackGrade(value) {
10421
- return typeof value === "number" && Number.isInteger(value) && GRADE_VALUES.includes(value);
10422
- }
10423
- function isAssessmentSessionTiming(value) {
10424
- return isObject(value) && typeof value.runId === "string" && isValidUUID(value.runId) && typeof value.resumeId === "string" && isValidUUID(value.resumeId) && isNonNegativeDuration(value.activeSeconds) && (value.inactiveSeconds === undefined || isNonNegativeDuration(value.inactiveSeconds));
10425
- }
10426
- function isNonNegativeDuration(value) {
10427
- return typeof value === "number" && Number.isFinite(value) && value >= 0;
10428
- }
10429
- function isAssessmentPurpose(value) {
10430
- return ASSESSMENT_PURPOSES.includes(value);
10431
- }
10432
- function isAssessmentResponseValue(value) {
10433
- const entries = Array.isArray(value) ? value : [value];
10434
- return entries.length > 0 && entries.every((entry) => typeof entry === "string" && entry.length > 0 && entry.length <= TIMEBACK_ASSESSMENT_RESPONSE_VALUE_MAX_LENGTH);
10435
- }
10436
- function applyAssessmentItemResponseUpdate(current, update) {
10437
- const next = { ...current };
10438
- for (const [responseId, value] of Object.entries(update)) {
10439
- if (value === null) {
10440
- delete next[responseId];
10441
- } else {
10442
- next[responseId] = value;
10443
- }
10444
- }
10445
- return Object.keys(next).length === 0 ? undefined : next;
10446
- }
10447
- function applyAssessmentResponseUpdate(current, update) {
10448
- const next = structuredClone(current);
10449
- for (const [questionId, questionUpdate] of Object.entries(update)) {
10450
- const questionResponses = applyAssessmentItemResponseUpdate(next[questionId], questionUpdate);
10451
- if (questionResponses === undefined) {
10452
- delete next[questionId];
10453
- } else {
10454
- next[questionId] = questionResponses;
10455
- }
10456
- }
10457
- return next;
10458
- }
10459
10436
  function assessmentResponseUpdateValidationMessage(assessment, update) {
10460
10437
  const items = new Map(assessment.items.map((item) => [item.identifier, item]));
10461
10438
  for (const [itemIdentifier, itemUpdate] of Object.entries(update)) {
@@ -10617,147 +10594,154 @@ function interactionResponseValidationMessage(itemIdentifier, interaction, respo
10617
10594
  }
10618
10595
  return null;
10619
10596
  }
10620
- function invalidResponse(message) {
10621
- return { code: "INVALID_RESPONSE", message };
10622
- }
10623
- function administeredItemIdentifiers(itemSubmissions) {
10624
- return new Set(itemSubmissions.map((submission) => submission.itemIdentifier));
10597
+ function emptyAssessmentTranscript(attemptId, contentRevision) {
10598
+ return { version: 1, attemptId, contentRevision, administrations: [] };
10625
10599
  }
10626
- function itemAdministration(itemSubmissions, itemIdentifier) {
10627
- return itemSubmissions.find((submission) => submission.itemIdentifier === itemIdentifier);
10628
- }
10629
- function responseValuesEqual(left, right) {
10630
- if (left === undefined || right === undefined) {
10631
- return left === right;
10632
- }
10633
- if (Array.isArray(left) || Array.isArray(right)) {
10634
- return Array.isArray(left) && Array.isArray(right) && left.length === right.length && left.every((value, index) => value === right[index]);
10635
- }
10636
- return left === right;
10637
- }
10638
- function itemResponsesEqual(left, right) {
10639
- const leftEntries = Object.entries(left ?? {});
10640
- const rightEntries = Object.entries(right ?? {});
10641
- return leftEntries.length === rightEntries.length && leftEntries.every(([identifier, value]) => responseValuesEqual(value, right?.[identifier]));
10642
- }
10643
- function itemResponseUpdateMatches(current, update) {
10644
- return itemResponsesEqual(current, applyAssessmentItemResponseUpdate(current, update));
10645
- }
10646
- function resolveAssessmentItemReplay(state, input) {
10647
- const flow = assessmentFlowForPurpose(state.purpose);
10648
- if (flow !== "item-submit") {
10600
+ function assessmentProgressAdministration(purpose, input) {
10601
+ if (purpose !== "mastery") {
10649
10602
  return {
10650
- action: "conflict",
10651
- failure: assessmentFlowViolation("Items cannot be submitted individually in this flow.", { flow })
10603
+ ok: false,
10604
+ failure: assessmentFlowViolation("Background progress is only available for mastery.")
10652
10605
  };
10653
10606
  }
10654
- const priorSubmission = state.itemSubmissions.find((submission) => submission.submissionId === input.submissionId);
10655
- if (priorSubmission) {
10656
- const sameRequest = priorSubmission.itemIdentifier === input.itemIdentifier && itemResponseUpdateMatches(state.responses[input.itemIdentifier], input.responses);
10657
- if (!sameRequest) {
10658
- return {
10659
- action: "conflict",
10660
- failure: assessmentFlowViolation("This item submission ID was already used for a different request.", {
10661
- submissionId: input.submissionId,
10662
- itemIdentifier: input.itemIdentifier,
10663
- committedItemIdentifier: priorSubmission.itemIdentifier,
10664
- flow
10665
- })
10666
- };
10667
- }
10607
+ const { administrations } = input.transcript;
10608
+ if (input.expectedResponseVersion !== administrations.length) {
10668
10609
  return {
10669
- action: "replay",
10670
- responseVersion: state.responseVersion,
10671
- responses: state.responses,
10672
- submission: priorSubmission
10610
+ ok: false,
10611
+ failure: assessmentResponseVersionConflict(input.expectedResponseVersion, administrations.length)
10673
10612
  };
10674
10613
  }
10675
- return { action: "missing" };
10614
+ const administration = administrations.find((entry) => entry.itemIdentifier === input.itemIdentifier);
10615
+ return administration ? { ok: true, submissionId: administration.submissionId } : {
10616
+ ok: false,
10617
+ failure: assessmentFlowViolation("The progress item must be present in the transcript.")
10618
+ };
10676
10619
  }
10677
- function prepareAssessmentItemSubmission(state, input) {
10678
- const replay = resolveAssessmentItemReplay(state, input);
10679
- if (replay.action === "conflict") {
10680
- return { action: "reject", failure: replay.failure };
10681
- }
10682
- if (replay.action === "replay") {
10683
- return replay;
10684
- }
10685
- if (state.responseVersion !== input.expectedResponseVersion) {
10620
+ function appendAssessmentAdministration(transcript, input) {
10621
+ const entry = {
10622
+ submissionId: input.submissionId,
10623
+ itemIdentifier: input.itemIdentifier,
10624
+ ...input.routingNodeKey === undefined ? {} : { routingNodeKey: input.routingNodeKey },
10625
+ responses: Object.fromEntries(Object.entries(input.responses).filter((pair) => pair[1] !== null))
10626
+ };
10627
+ const priorIndex = transcript.administrations.findIndex((item) => item.submissionId === input.submissionId);
10628
+ const index = priorIndex === -1 ? transcript.administrations.length : priorIndex;
10629
+ if (input.expectedResponseVersion !== index) {
10686
10630
  return {
10687
- action: "reject",
10688
- failure: assessmentResponseVersionConflict(input.expectedResponseVersion, state.responseVersion)
10631
+ ok: false,
10632
+ failure: assessmentResponseVersionConflict(input.expectedResponseVersion, index)
10689
10633
  };
10690
10634
  }
10691
- const itemIndex = state.assessment.items.findIndex((item) => item.identifier === input.itemIdentifier);
10692
- const previousItemIdentifier = state.itemSubmissions.at(-1)?.itemIdentifier;
10693
- const previousItemIndex = state.assessment.items.findIndex((item) => item.identifier === previousItemIdentifier);
10694
- if (itemIndex === -1 || itemIndex <= previousItemIndex) {
10635
+ if (priorIndex !== -1 && canonicalJson(transcript.administrations[priorIndex]) !== canonicalJson(entry)) {
10695
10636
  return {
10696
- action: "reject",
10697
- failure: assessmentFlowViolation("Items must be submitted once and in candidate order.", {
10698
- itemIdentifier: input.itemIdentifier,
10699
- previousItemIdentifier: previousItemIdentifier ?? null,
10700
- flow: assessmentFlowForPurpose(state.purpose)
10701
- })
10637
+ ok: false,
10638
+ failure: assessmentFlowViolation("This administration identity was reused with different answers.")
10702
10639
  };
10703
10640
  }
10704
- const update = { [input.itemIdentifier]: input.responses };
10705
- const updateMessage = assessmentResponseUpdateValidationMessage(state.assessment, update);
10706
- if (updateMessage) {
10707
- return { action: "reject", failure: invalidResponse(updateMessage) };
10708
- }
10709
- const responses = applyAssessmentResponseUpdate(state.responses, update);
10710
- const validationMessage = assessmentResponseValidationMessage(state.assessment, responses);
10711
- if (validationMessage) {
10712
- return { action: "reject", failure: invalidResponse(validationMessage) };
10713
- }
10714
10641
  return {
10715
- action: "commit",
10716
- itemIndex,
10717
- responseVersion: state.responseVersion + 1,
10718
- responses,
10719
- answered: Object.keys(responses[input.itemIdentifier] ?? {}).length > 0
10642
+ ok: true,
10643
+ index,
10644
+ transcript: priorIndex === -1 ? { ...transcript, administrations: [...transcript.administrations, entry] } : transcript
10720
10645
  };
10721
10646
  }
10722
- function completeAssessmentItemSubmission(state, input, prepared, scoring, submittedAt) {
10723
- const submission = {
10724
- submissionId: input.submissionId,
10725
- itemIdentifier: input.itemIdentifier,
10726
- submittedAt,
10727
- responseVersion: prepared.responseVersion,
10728
- answered: prepared.answered,
10729
- score: scoring.score,
10730
- isCorrect: scoring.isCorrect,
10731
- ...scoring.grading ? { grading: scoring.grading } : {}
10732
- };
10733
- return {
10734
- responseVersion: prepared.responseVersion,
10735
- responses: prepared.responses,
10736
- itemSubmissions: [...state.itemSubmissions, submission],
10737
- submission
10738
- };
10647
+ function prepareAssessmentTranscript(input) {
10648
+ const parsed = AssessmentTranscriptSchema.safeParse(input.transcript);
10649
+ if (!parsed.success) {
10650
+ throw new AssessmentTranscriptError("Invalid assessment transcript", 0, "INVALID_RESPONSE");
10651
+ }
10652
+ const transcript = parsed.data;
10653
+ if (transcript.attemptId !== input.attemptId || transcript.contentRevision !== input.contentRevision) {
10654
+ throw new AssessmentTranscriptError("Transcript does not belong to this attempt and revision");
10655
+ }
10656
+ const responses = {};
10657
+ const submissions = new Set;
10658
+ const items = new Set;
10659
+ let previousItemIndex = -1;
10660
+ for (const [index, entry] of transcript.administrations.entries()) {
10661
+ const itemIndex = input.assessment.items.findIndex((item) => item.identifier === entry.itemIdentifier);
10662
+ if (submissions.has(entry.submissionId) || items.has(entry.itemIdentifier)) {
10663
+ throw new AssessmentTranscriptError("Transcript contains a repeated administration or item", index);
10664
+ }
10665
+ if (itemIndex === -1) {
10666
+ throw new AssessmentTranscriptError("Transcript references an unknown assessment item", index, "INVALID_RESPONSE");
10667
+ }
10668
+ if (!input.routed && itemIndex <= previousItemIndex) {
10669
+ throw new AssessmentTranscriptError("Transcript items must follow the selected candidate order", index);
10670
+ }
10671
+ if (input.routed !== (entry.routingNodeKey !== undefined)) {
10672
+ throw new AssessmentTranscriptError("Transcript routing identity does not match the assessment flow", index);
10673
+ }
10674
+ const itemResponses = { [entry.itemIdentifier]: entry.responses };
10675
+ const validate2 = input.routed || input.allowIncomplete ? assessmentDraftResponseValidationMessage : assessmentResponseValidationMessage;
10676
+ const invalid = Object.keys(entry.responses).length === 0 ? null : validate2(input.assessment, itemResponses);
10677
+ if (invalid) {
10678
+ throw new AssessmentTranscriptError(invalid, index, "INVALID_RESPONSE");
10679
+ }
10680
+ submissions.add(entry.submissionId);
10681
+ items.add(entry.itemIdentifier);
10682
+ previousItemIndex = itemIndex;
10683
+ Object.assign(responses, itemResponses);
10684
+ }
10685
+ return { transcript, responses };
10686
+ }
10687
+ function completeAssessmentTranscript(input) {
10688
+ const itemSubmissions = [];
10689
+ let state;
10690
+ const ledger = [];
10691
+ if (input.manifest) {
10692
+ const initial = initializeDiagnosticRouting(input.manifest);
10693
+ if (!initial.ok) {
10694
+ throw new AssessmentTranscriptError("The pinned diagnostic routing manifest is invalid");
10695
+ }
10696
+ state = initial.state;
10697
+ }
10698
+ const scores = new Map(input.scores.map((score) => [score.itemIdentifier, score]));
10699
+ for (const [index, entry] of input.transcript.administrations.entries()) {
10700
+ const scoring = scores.get(entry.itemIdentifier);
10701
+ if (!scoring) {
10702
+ throw new AssessmentTranscriptError("Transcript item has no trusted grade", index);
10703
+ }
10704
+ if (input.manifest) {
10705
+ if (typeof scoring.isCorrect !== "boolean" || !entry.routingNodeKey) {
10706
+ throw new AssessmentTranscriptError("Diagnostic grading must be determinate", index);
10707
+ }
10708
+ const advanced = advanceDiagnosticRouting(input.manifest, ledger, {
10709
+ routingNodeKey: entry.routingNodeKey,
10710
+ itemIdentifier: entry.itemIdentifier,
10711
+ isCorrect: scoring.isCorrect
10712
+ });
10713
+ if (!advanced.ok) {
10714
+ throw new AssessmentTranscriptError("Transcript does not follow the pinned diagnostic route", index);
10715
+ }
10716
+ ledger.push(advanced.ledgerEntry);
10717
+ state = advanced.state;
10718
+ }
10719
+ itemSubmissions.push({
10720
+ submissionId: entry.submissionId,
10721
+ itemIdentifier: entry.itemIdentifier,
10722
+ responseVersion: index + 1,
10723
+ submittedAt: input.submittedAt,
10724
+ answered: Object.keys(entry.responses).length > 0,
10725
+ score: scoring.score,
10726
+ isCorrect: scoring.isCorrect,
10727
+ ...scoring.grading ? { grading: scoring.grading } : {}
10728
+ });
10729
+ }
10730
+ if (input.terminal) {
10731
+ const count = itemSubmissions.length;
10732
+ const conventionalComplete = assessmentFlowForPurpose(input.purpose) === "item-submit" ? count > 0 : count === input.itemCount;
10733
+ const complete = state ? state.status === "ready-to-complete" && count > 0 : conventionalComplete;
10734
+ if (!complete) {
10735
+ throw new AssessmentTranscriptError("A complete administered transcript is required for submission", count);
10736
+ }
10737
+ }
10738
+ return { itemSubmissions, ledger, state };
10739
10739
  }
10740
- function assessmentResponseUpdateFlowFailure(purpose, itemSubmissions, update) {
10741
- const administered = administeredItemIdentifiers(itemSubmissions);
10742
- const committedUpdate = Object.keys(update).find((identifier) => administered.has(identifier));
10743
- return committedUpdate ? assessmentFlowViolation("A submitted item response cannot be edited.", {
10744
- itemIdentifier: committedUpdate,
10745
- flow: assessmentFlowForPurpose(purpose)
10746
- }) : null;
10740
+ function administeredItemIdentifiers(itemSubmissions) {
10741
+ return new Set(itemSubmissions.map((submission) => submission.itemIdentifier));
10747
10742
  }
10748
- function assessmentCompletionFlowFailure(purpose, itemSubmissions, itemCount) {
10749
- const flow = assessmentFlowForPurpose(purpose);
10750
- if (flow !== "item-submit") {
10751
- return null;
10752
- }
10753
- if (itemSubmissions.length > 0 && itemSubmissions.length <= itemCount) {
10754
- return null;
10755
- }
10756
- return assessmentFlowViolation("At least one candidate must be administered before the review can be completed.", {
10757
- flow,
10758
- submittedItemCount: itemSubmissions.length,
10759
- itemCount
10760
- });
10743
+ function itemAdministration(itemSubmissions, itemIdentifier) {
10744
+ return itemSubmissions.find((submission) => submission.itemIdentifier === itemIdentifier);
10761
10745
  }
10762
10746
  function selectAssessmentCourseCandidate(candidates, subjectSpecified) {
10763
10747
  if (candidates.length === 0) {
@@ -11090,9 +11074,6 @@ function reviewRequestFingerprint(request, policy) {
11090
11074
  function reviewQuestionLineItemId(parentLineItemId, itemIdentifier) {
11091
11075
  return deterministicUUID(["playcademy:review-question-line-item:v1", parentLineItemId, itemIdentifier].join("\x00"));
11092
11076
  }
11093
- function reviewQuestionResultId(parentAttemptId, childQuestionLineItemId) {
11094
- return deterministicUUID(["playcademy:review-question-result:v1", parentAttemptId, childQuestionLineItemId].join("\x00"));
11095
- }
11096
11077
  function qtiQuestionByIdentifier(questions) {
11097
11078
  const byIdentifier = new Map;
11098
11079
  for (const entry of questions.questions) {
@@ -11652,11 +11633,8 @@ function isDiagnosticRoutingState(value) {
11652
11633
  const nextValid = value.next === null || isRecord(value.next) && isNonEmptyString(value.next.stageKey) && isNonEmptyString(value.next.trackKey) && isNonEmptyString(value.next.nodeKey) && isNonEmptyString(value.next.itemIdentifier);
11653
11634
  return stagesValid && tracksValid && nextValid && (value.status === "in-progress" && value.next !== null || value.status === "ready-to-complete" && value.next === null);
11654
11635
  }
11655
- function isDiagnosticRoutingSnapshot(value, revision) {
11656
- return isRecord(value) && value.kind === "platform-routed-diagnostic" && value.revision === revision && (value.status === "in-progress" || value.status === "ready-to-complete") && (value.next === null || isRecord(value.next) && isNonEmptyString(value.next.stageKey) && isNonEmptyString(value.next.trackKey) && isNonEmptyString(value.next.nodeKey) && isNonEmptyString(value.next.itemIdentifier)) && Array.isArray(value.tracks) && value.tracks.every((track) => isRecord(track) && isNonEmptyString(track.trackKey) && (track.groupKey === undefined || isNonEmptyString(track.groupKey)) && ["pending", "active", "completed", "skipped"].includes(String(track.status)) && Number.isInteger(track.administeredCount) && Number(track.administeredCount) >= 0);
11657
- }
11658
11636
  function isRoutedDiagnosticAttemptMetadata(value, itemSubmissions, responseVersion) {
11659
- if (!isRecord(value) || !isNonEmptyString(value.definitionId) || !isNonEmptyString(value.diagnosticKey) || !isNonEmptyString(value.testName) || !isNonEmptyString(value.routingRevision) || !Array.isArray(value.ledger) || !value.ledger.every(isDiagnosticRoutingLedgerEntry) || value.routingSnapshots !== undefined && (!Array.isArray(value.routingSnapshots) || value.routingSnapshots.length !== value.ledger.length || !value.routingSnapshots.every((snapshot) => isDiagnosticRoutingSnapshot(snapshot, String(value.routingRevision)))) || !isDiagnosticRoutingState(value.state) || value.ledger.length !== itemSubmissions.length || responseVersion !== itemSubmissions.length) {
11637
+ if (!isRecord(value) || !isNonEmptyString(value.definitionId) || !isNonEmptyString(value.diagnosticKey) || !isNonEmptyString(value.testName) || !isNonEmptyString(value.routingRevision) || !Array.isArray(value.ledger) || !value.ledger.every(isDiagnosticRoutingLedgerEntry) || !isDiagnosticRoutingState(value.state) || value.ledger.length !== itemSubmissions.length || responseVersion !== itemSubmissions.length) {
11660
11638
  return false;
11661
11639
  }
11662
11640
  const itemIdentifiers = new Set;
@@ -11710,19 +11688,7 @@ function isPlaycademyAssessmentResultMetadataV1(value) {
11710
11688
  const review = metadata2.review;
11711
11689
  const diagnostic = metadata2.diagnostic;
11712
11690
  const routedDiagnostic = metadata2.purpose === "diagnostic" && diagnostic !== undefined && Array.isArray(metadata2.itemSubmissions) && Number.isInteger(metadata2.responseVersion) && isRoutedDiagnosticAttemptMetadata(diagnostic, metadata2.itemSubmissions, metadata2.responseVersion);
11713
- return metadata2.version === 1 && typeof metadata2.activityId === "string" && isAssessmentPurpose(metadata2.purpose) && typeof metadata2.courseId === "string" && typeof metadata2.integrationId === "string" && typeof metadata2.enrollmentId === "string" && Boolean(selectedTest) && (selectedTest?.assessmentKey === undefined || isNonEmptyString(selectedTest.assessmentKey)) && typeof selectedTest?.identifier === "string" && typeof selectedTest.contentRevision === "string" && Number.isInteger(metadata2.attemptNumber) && Number.isInteger(metadata2.responseVersion) && Boolean(metadata2.responses) && typeof metadata2.responses === "object" && Array.isArray(metadata2.itemSubmissions) && metadata2.itemSubmissions.every(isAssessmentItemSubmission) && typeof metadata2.startedAt === "string" && typeof metadata2.updatedAt === "string" && (metadata2.finalizedAt === undefined || typeof metadata2.finalizedAt === "string") && isAssessmentAwardRecord(metadata2.award) && (metadata2.purpose === "mastery" ? isMasteryAttemptMetadata(mastery) : mastery === undefined) && (metadata2.purpose === "review" ? isReviewAttemptMetadata(review) : review === undefined) && (metadata2.purpose === "diagnostic" ? diagnostic === undefined || routedDiagnostic : diagnostic === undefined) && (metadata2.submissionId === undefined || typeof metadata2.submissionId === "string") && (metadata2.score === undefined || isAssessmentScore(metadata2.score)) && isCompletionMetadata(metadata2.completion, metadata2.purpose, routedDiagnostic);
11714
- }
11715
- function isPlaycademyReviewAssessmentItemResultMetadataV1(value) {
11716
- if (!isRecord(value) || !isRecord(value.responses)) {
11717
- return false;
11718
- }
11719
- return value.version === 1 && typeof value.parentAttemptId === "string" && typeof value.activityId === "string" && typeof value.integrationId === "string" && typeof value.enrollmentId === "string" && typeof value.bankRevision === "string" && typeof value.itemIdentifier === "string" && isAssessmentStandardRef(value.standard) && typeof value.deliveredAt === "string" && Number.isInteger(value.responseVersion) && Object.values(value.responses).every(isAssessmentResponseValue) && (value.submissionId === undefined || typeof value.submissionId === "string") && (value.isCorrect === undefined || value.isCorrect === null || typeof value.isCorrect === "boolean");
11720
- }
11721
- function isPlaycademyDiagnosticAssessmentItemResultMetadataV1(value) {
11722
- if (!isRecord(value) || !isRecord(value.responses)) {
11723
- return false;
11724
- }
11725
- return value.version === 1 && isNonEmptyString(value.parentAttemptId) && isNonEmptyString(value.activityId) && isNonEmptyString(value.integrationId) && isNonEmptyString(value.enrollmentId) && isNonEmptyString(value.definitionId) && isNonEmptyString(value.diagnosticKey) && isNonEmptyString(value.routingRevision) && isNonEmptyString(value.contentRevision) && isNonEmptyString(value.stageKey) && isNonEmptyString(value.trackKey) && isNonEmptyString(value.routingNodeKey) && isNonEmptyString(value.itemIdentifier) && isNonEmptyString(value.deliveredAt) && Number.isInteger(value.responseVersion) && Number(value.responseVersion) > 0 && Object.values(value.responses).every(isAssessmentResponseValue) && isNonEmptyString(value.submissionId) && typeof value.isCorrect === "boolean";
11691
+ return metadata2.version === 1 && typeof metadata2.activityId === "string" && isAssessmentPurpose(metadata2.purpose) && typeof metadata2.courseId === "string" && typeof metadata2.integrationId === "string" && typeof metadata2.enrollmentId === "string" && Boolean(selectedTest) && (selectedTest?.assessmentKey === undefined || isNonEmptyString(selectedTest.assessmentKey)) && typeof selectedTest?.identifier === "string" && typeof selectedTest.contentRevision === "string" && Number.isInteger(metadata2.attemptNumber) && Number.isInteger(metadata2.responseVersion) && Boolean(metadata2.responses) && typeof metadata2.responses === "object" && Array.isArray(metadata2.itemSubmissions) && metadata2.itemSubmissions.every(isAssessmentItemSubmission) && typeof metadata2.startedAt === "string" && typeof metadata2.updatedAt === "string" && (metadata2.finalizedAt === undefined || typeof metadata2.finalizedAt === "string") && isAssessmentAwardRecord(metadata2.award) && (metadata2.purpose === "mastery" ? isMasteryAttemptMetadata(mastery) : mastery === undefined) && (metadata2.purpose === "review" ? isReviewAttemptMetadata(review) : review === undefined) && (metadata2.purpose === "diagnostic" ? diagnostic === undefined || routedDiagnostic : diagnostic === undefined) && (metadata2.submissionId === undefined || typeof metadata2.submissionId === "string") && (metadata2.score === undefined || isAssessmentScore(metadata2.score)) && (metadata2.transcript === undefined || AssessmentTranscriptSchema.safeParse(metadata2.transcript).success) && isCompletionMetadata(metadata2.completion, metadata2.purpose, routedDiagnostic);
11726
11692
  }
11727
11693
  function playcademyAssessmentResultMetadata(value) {
11728
11694
  if (!isRecord(value)) {
@@ -11742,12 +11708,6 @@ function playcademyAssessmentResultMetadata(value) {
11742
11708
  diagnostic: { ...normalized.diagnostic, ledger: [] }
11743
11709
  };
11744
11710
  }
11745
- if (isRecord(normalized.diagnostic) && Array.isArray(normalized.diagnostic.ledger) && normalized.diagnostic.ledger.length === 0 && normalized.diagnostic.routingSnapshots === undefined) {
11746
- normalized = {
11747
- ...normalized,
11748
- diagnostic: { ...normalized.diagnostic, routingSnapshots: [] }
11749
- };
11750
- }
11751
11711
  if (isRecord(normalized.diagnostic) && isRecord(normalized.diagnostic.state)) {
11752
11712
  const state = normalized.diagnostic.state;
11753
11713
  const tracks = Array.isArray(state.tracks) ? state.tracks.map((track) => isRecord(track) ? {
@@ -11755,12 +11715,10 @@ function playcademyAssessmentResultMetadata(value) {
11755
11715
  currentNodeKey: track.currentNodeKey ?? null,
11756
11716
  terminal: track.terminal ?? null
11757
11717
  } : track) : state.tracks;
11758
- const routingSnapshots = Array.isArray(normalized.diagnostic.routingSnapshots) ? normalized.diagnostic.routingSnapshots.map((snapshot) => isRecord(snapshot) ? { ...snapshot, next: snapshot.next ?? null } : snapshot) : normalized.diagnostic.routingSnapshots;
11759
11718
  normalized = {
11760
11719
  ...normalized,
11761
11720
  diagnostic: {
11762
11721
  ...normalized.diagnostic,
11763
- ...routingSnapshots === undefined ? {} : { routingSnapshots },
11764
11722
  state: {
11765
11723
  ...state,
11766
11724
  ...tracks === undefined ? {} : { tracks },
@@ -11770,6 +11728,16 @@ function playcademyAssessmentResultMetadata(value) {
11770
11728
  };
11771
11729
  }
11772
11730
  const review = normalized.review;
11731
+ if (isRecord(normalized.transcript)) {
11732
+ const transcript = normalized.transcript;
11733
+ normalized = {
11734
+ ...normalized,
11735
+ transcript: {
11736
+ ...transcript,
11737
+ administrations: Array.isArray(transcript.administrations) ? transcript.administrations.map((entry) => isRecord(entry) ? { ...entry, responses: entry.responses ?? {} } : entry) : transcript.administrations ?? []
11738
+ }
11739
+ };
11740
+ }
11773
11741
  if (isRecord(review) && isRecord(review.fulfillment) && review.fulfillment.status === "complete" && review.fulfillment.shortages === undefined) {
11774
11742
  normalized = {
11775
11743
  ...normalized,
@@ -11784,29 +11752,7 @@ function playcademyAssessmentResultMetadata(value) {
11784
11752
  }
11785
11753
  return isPlaycademyAssessmentResultMetadataV1(normalized) ? normalized : null;
11786
11754
  }
11787
- function playcademyAssessmentItemResultMetadata(value) {
11788
- if (!isRecord(value)) {
11789
- return null;
11790
- }
11791
- const metadata2 = value[PLAYCADEMY_ASSESSMENT_ITEM_RESULT_METADATA_KEY];
11792
- if (!isRecord(metadata2)) {
11793
- return null;
11794
- }
11795
- const normalized = metadata2.responses === undefined ? { ...metadata2, responses: {} } : metadata2;
11796
- return isPlaycademyReviewAssessmentItemResultMetadataV1(normalized) ? normalized : null;
11797
- }
11798
- function playcademyDiagnosticAssessmentItemResultMetadata(value) {
11799
- if (!isRecord(value)) {
11800
- return null;
11801
- }
11802
- const metadata2 = value[PLAYCADEMY_ASSESSMENT_ITEM_RESULT_METADATA_KEY];
11803
- if (!isRecord(metadata2)) {
11804
- return null;
11805
- }
11806
- const normalized = metadata2.responses === undefined ? { ...metadata2, responses: {} } : metadata2;
11807
- return isPlaycademyDiagnosticAssessmentItemResultMetadataV1(normalized) ? normalized : null;
11808
- }
11809
- var SCRIPT_SCHEMES, DATA_RASTER_IMAGE_PATTERN, QTI_MATHML_ALLOWED_TAGS, metadataSymbol, parser, SUPPORTED_QTI_INTERACTION_TYPES, URL_ATTRIBUTES, IMAGE_URL_ATTRIBUTES, INTERACTION_TYPES, EMPHASIS_TAGS, STRONG_TAGS, CONTENT_SKIPPED_TAGS, BLOCK_CONTENT_KINDS, GAP_MATCH_TOKEN_TAGS, STAGE_GRAPHIC_EXCLUDED_CONTAINERS, BLANK_SENTINEL = "￿", MARKED_BLANK_PATTERN, MARKED_BLANK_GLOBAL_PATTERN, MATCH_CORRECT_TEMPLATE = "http://www.imsglobal.org/question/qti_v3p0/rptemplates/match_correct", MAP_RESPONSE_TEMPLATE = "https://purl.imsglobal.org/spec/qti/v3p0/rptemplates/map_response", MAP_RESPONSE_TEMPLATES, RESPONSE_PROCESSING_TEMPLATES, VOID_BODY_ELEMENTS, URL_ATTRIBUTES2, IMAGE_URL_ATTRIBUTES2, VOID_CHOICE_ELEMENTS, NUMERIC_COMPARISON_ATTRIBUTES, RESPONSE_CARDINALITIES, RESPONSE_BASE_TYPES, POINT_INTERACTION_TYPES, INDEPENDENT_PROCESSING_TAGS, PRIVATE_PLAYABLE_KEYS, trimmedNonEmptyString, finiteNumber2, positiveFiniteNumber, IDENTIFIER_VALUE, DIRECTED_PAIR_VALUE, scoringMatchComparisonSchema, scoringNumericEqualComparisonSchema, scoringNumericEqualRoundedComparisonSchema, scoringRuleSchema, scoringRulesSchema, scoringItemSchema, assessmentScoringArtifactItemsSchema, assessmentScoringArtifactSchema, SUPPORTED_BASE_TYPES, SUPPORTED_CARDINALITIES, MATCH_CORRECT_TEMPLATES, UUID_PATTERN, COMMON_CORE_MATH_FRAMEWORK_ALIASES, COMMON_CORE_ELA_FRAMEWORK_ALIASES, COMMON_CORE_FRAMEWORK_ALIASES, ASSESSMENT_ATTEMPT_OPEN, ASSESSMENT_ATTEMPT_COMPLETED, ASSESSMENT_ATTEMPT_AWAITING_AWARD, ASSESSMENT_ATTEMPT_SUPERSEDED, ASSESSMENT_RUNTIME_ERROR_STATUS, ROUTING_KEY_MAX_LENGTH = 128, ROUTING_RESULT_KEY_MAX_LENGTH = 256, ROUTING_STAGE_LIMIT = 32, ROUTING_TRACK_LIMIT = 128, ROUTING_NODE_LIMIT = 1000, ROUTING_TERMINAL_LIMIT = 1000, ROUTING_GROUP_LIMIT = 128, ROUTING_PREDICATE_DEPTH_LIMIT = 8, ROUTING_EXPLORATION_STATE_LIMIT = 200000, RoutingKeySchema, RoutingResultKeySchema, DiagnosticTrackOutcomeSchema, DiagnosticActivationPredicateSchema, DiagnosticRoutingTransitionSchema, DiagnosticRoutingManifestV1Schema, GRADE_VALUES, POINT_RESPONSE_PATTERN, ZERO_WIDTH_CHARACTERS, CURLY_SINGLE_QUOTES, CURLY_DOUBLE_QUOTES, UNICODE_DASHES, ARITHMETIC_OPERATOR_SPACING, THOUSANDS_GROUP, REVIEW_SELECTION_POLICY_VERSION = "unseen-first-least-recently-delivered-v1", PLAYCADEMY_REVIEW_BANK_MANIFEST_METADATA_KEY = "playcademyReviewBank", PLAYCADEMY_REVIEW_BANK_MANIFEST_VERSION = 1, DEFAULT_REVIEW_SELECTION_POLICY, ASSESSMENT_RUNTIME_FIXTURE_BUNDLE_VERSION = 8, ASSESSMENT_CHILD_RESULT_KIND = "assessment-child-result", ASSESSMENT_CHILD_RESULT_VERSION = 1, ASSESSMENT_CHILD_RESULT_ID_NAMESPACE = "playcademy:assessment-child-result:v1", IdentitySchema, RecordedAtSchema, ResponseKeySchema, AssessmentResponseValueSchema, AssessmentScoreSchema, AssessmentStandardRefSchema, DiagnosticRoutingLedgerEntryV1Schema, AssessmentChildResultAttemptV1Schema, AssessmentChildResultAdministrationV1Schema, AssessmentChildResultGradingV1Schema, ReviewChildResultContextV1Schema, DiagnosticChildResultContextV1Schema, CommonPayloadShape, ReviewChildResultPayloadV1Schema, DiagnosticChildResultPayloadV1Schema, AssessmentChildResultPayloadV1Schema, AssessmentChildResultCommandV1Schema, RuntimeSubjectSchema, RuntimeGradeSchema, OptionalQueryGradeSchema, AssessmentResponseValueSchema2, AssessmentRuntimeIdentitySchema, ResponseKeySchema2, StartAssessmentBaseSchema, AssessmentStandardRefSchema2, LatestAssessmentFilterBaseSchema, LatestAssessmentFiltersSchema, LatestRuntimeAssessmentQuerySchema, StartAssessmentBodySchema, AssessmentPreparationReceiptSchema, StartAssessmentRequestBodySchema, SaveAssessmentBodySchema, SubmitAssessmentItemBodySchema, SubmitAssessmentBodySchema, FinalizeAssessmentBodySchema, PrepareRuntimeAssessmentRequestSchema, StartRuntimeAssessmentRequestSchema, SaveRuntimeAssessmentRequestSchema, SubmitAssessmentItemRuntimeRequestSchema, SubmitRuntimeAssessmentRequestSchema, FinalizeRuntimeAssessmentRequestSchema;
11755
+ var SCRIPT_SCHEMES, DATA_RASTER_IMAGE_PATTERN, QTI_MATHML_ALLOWED_TAGS, metadataSymbol, parser, SUPPORTED_QTI_INTERACTION_TYPES, URL_ATTRIBUTES, IMAGE_URL_ATTRIBUTES, INTERACTION_TYPES, EMPHASIS_TAGS, STRONG_TAGS, CONTENT_SKIPPED_TAGS, BLOCK_CONTENT_KINDS, GAP_MATCH_TOKEN_TAGS, STAGE_GRAPHIC_EXCLUDED_CONTAINERS, BLANK_SENTINEL = "￿", MARKED_BLANK_PATTERN, MARKED_BLANK_GLOBAL_PATTERN, MATCH_CORRECT_TEMPLATE = "http://www.imsglobal.org/question/qti_v3p0/rptemplates/match_correct", MAP_RESPONSE_TEMPLATE = "https://purl.imsglobal.org/spec/qti/v3p0/rptemplates/map_response", MAP_RESPONSE_TEMPLATES, RESPONSE_PROCESSING_TEMPLATES, VOID_BODY_ELEMENTS, URL_ATTRIBUTES2, IMAGE_URL_ATTRIBUTES2, VOID_CHOICE_ELEMENTS, NUMERIC_COMPARISON_ATTRIBUTES, RESPONSE_CARDINALITIES, RESPONSE_BASE_TYPES, POINT_INTERACTION_TYPES, INDEPENDENT_PROCESSING_TAGS, PRIVATE_PLAYABLE_KEYS, trimmedNonEmptyString, finiteNumber2, positiveFiniteNumber, IDENTIFIER_VALUE, DIRECTED_PAIR_VALUE, scoringMatchComparisonSchema, scoringNumericEqualComparisonSchema, scoringNumericEqualRoundedComparisonSchema, scoringRuleSchema, scoringRulesSchema, scoringItemSchema, assessmentScoringArtifactItemsSchema, assessmentScoringArtifactSchema, SUPPORTED_BASE_TYPES, SUPPORTED_CARDINALITIES, MATCH_CORRECT_TEMPLATES, UUID_PATTERN, COMMON_CORE_MATH_FRAMEWORK_ALIASES, COMMON_CORE_ELA_FRAMEWORK_ALIASES, COMMON_CORE_FRAMEWORK_ALIASES, ASSESSMENT_ATTEMPT_OPEN, ASSESSMENT_ATTEMPT_COMPLETED, ASSESSMENT_ATTEMPT_AWAITING_AWARD, ASSESSMENT_ATTEMPT_SUPERSEDED, ASSESSMENT_RUNTIME_ERROR_STATUS, GRADE_VALUES, ROUTING_KEY_MAX_LENGTH = 128, ROUTING_RESULT_KEY_MAX_LENGTH = 256, ROUTING_STAGE_LIMIT = 32, ROUTING_TRACK_LIMIT = 128, ROUTING_NODE_LIMIT = 1000, ROUTING_TERMINAL_LIMIT = 1000, ROUTING_GROUP_LIMIT = 128, ROUTING_PREDICATE_DEPTH_LIMIT = 8, ROUTING_EXPLORATION_STATE_LIMIT = 200000, RoutingKeySchema, RoutingResultKeySchema, DiagnosticTrackOutcomeSchema, DiagnosticActivationPredicateSchema, DiagnosticRoutingTransitionSchema, DiagnosticRoutingManifestV1Schema, POINT_RESPONSE_PATTERN, Identity, AssessmentTranscriptSchema, AssessmentTranscriptError, ZERO_WIDTH_CHARACTERS, CURLY_SINGLE_QUOTES, CURLY_DOUBLE_QUOTES, UNICODE_DASHES, ARITHMETIC_OPERATOR_SPACING, THOUSANDS_GROUP, REVIEW_SELECTION_POLICY_VERSION = "unseen-first-least-recently-delivered-v1", PLAYCADEMY_REVIEW_BANK_MANIFEST_METADATA_KEY = "playcademyReviewBank", PLAYCADEMY_REVIEW_BANK_MANIFEST_VERSION = 1, DEFAULT_REVIEW_SELECTION_POLICY, ASSESSMENT_RUNTIME_FIXTURE_BUNDLE_VERSION = 8, ASSESSMENT_CHILD_RESULT_KIND = "assessment-child-result", ASSESSMENT_CHILD_RESULT_VERSION = 1, ASSESSMENT_CHILD_RESULT_ID_NAMESPACE = "playcademy:assessment-child-result:v1", IdentitySchema, RecordedAtSchema, ResponseKeySchema, AssessmentResponseValueSchema, AssessmentScoreSchema, AssessmentStandardRefSchema, DiagnosticRoutingLedgerEntryV1Schema, AssessmentChildResultAttemptV1Schema, AssessmentChildResultAdministrationV1Schema, AssessmentChildResultGradingV1Schema, ReviewChildResultContextV1Schema, FixedChildResultContextV1Schema, DiagnosticChildResultContextV1Schema, CommonPayloadShape, ReviewChildResultPayloadV1Schema, FixedChildResultPayloadV1Schema, DiagnosticChildResultPayloadV1Schema, AssessmentChildResultPayloadV1Schema, AssessmentChildResultCommandV1Schema, RuntimeSubjectSchema, RuntimeGradeSchema, OptionalQueryGradeSchema, AssessmentResponseValueSchema2, AssessmentRuntimeIdentitySchema, ResponseKeySchema2, StartAssessmentBaseSchema, AssessmentStandardRefSchema2, LatestAssessmentFilterBaseSchema, LatestAssessmentFiltersSchema, LatestRuntimeAssessmentQuerySchema, StartAssessmentBodySchema, AssessmentPreparationReceiptSchema, StartAssessmentRequestBodySchema, SaveAssessmentBodySchema, RecordAssessmentProgressBodySchema, ResumeAssessmentBodySchema, ResumeRuntimeAssessmentRequestSchema, SubmitAssessmentItemBodySchema, SubmitAssessmentBodySchema, FinalizeAssessmentBodySchema, PrepareRuntimeAssessmentRequestSchema, StartRuntimeAssessmentRequestSchema, SaveRuntimeAssessmentRequestSchema, RecordRuntimeAssessmentProgressRequestSchema, SubmitAssessmentItemRuntimeRequestSchema, SubmitRuntimeAssessmentRequestSchema, FinalizeRuntimeAssessmentRequestSchema;
11810
11756
  var init_assessment_runtime2 = __esm(() => {
11811
11757
  init_timeback3();
11812
11758
  init_timeback3();
@@ -11816,13 +11762,14 @@ var init_assessment_runtime2 = __esm(() => {
11816
11762
  init_timeback3();
11817
11763
  init_esm();
11818
11764
  init_esm();
11765
+ init_src();
11766
+ init_uuid();
11767
+ init_esm();
11819
11768
  init_timeback4();
11820
11769
  init_src();
11821
11770
  init_timeback3();
11822
11771
  init_src();
11823
11772
  init_uuid();
11824
- init_src();
11825
- init_uuid();
11826
11773
  init_esm();
11827
11774
  init_src();
11828
11775
  init_uuid();
@@ -12118,6 +12065,7 @@ var init_assessment_runtime2 = __esm(() => {
12118
12065
  ATTEMPT_ALREADY_SUBMITTED: 409,
12119
12066
  ASSESSMENT_FLOW_VIOLATION: 409
12120
12067
  };
12068
+ GRADE_VALUES = TIMEBACK_GRADES;
12121
12069
  RoutingKeySchema = exports_external.string().trim().min(1).max(ROUTING_KEY_MAX_LENGTH);
12122
12070
  RoutingResultKeySchema = exports_external.string().trim().min(1).max(ROUTING_RESULT_KEY_MAX_LENGTH);
12123
12071
  DiagnosticTrackOutcomeSchema = exports_external.enum(DIAGNOSTIC_TRACK_OUTCOMES);
@@ -12175,8 +12123,31 @@ var init_assessment_runtime2 = __esm(() => {
12175
12123
  resultKey: RoutingResultKeySchema
12176
12124
  }).strict()).min(1).max(ROUTING_TERMINAL_LIMIT)
12177
12125
  }).strict();
12178
- GRADE_VALUES = TIMEBACK_GRADES;
12179
12126
  POINT_RESPONSE_PATTERN = /^-?\d+(?:\.\d+)? -?\d+(?:\.\d+)?$/;
12127
+ Identity = exports_external.string().min(1).max(512).refine((value) => value.trim() === value);
12128
+ AssessmentTranscriptSchema = exports_external.object({
12129
+ version: exports_external.literal(1),
12130
+ attemptId: Identity,
12131
+ contentRevision: Identity,
12132
+ administrations: exports_external.array(exports_external.object({
12133
+ submissionId: Identity,
12134
+ itemIdentifier: Identity,
12135
+ routingNodeKey: Identity.optional(),
12136
+ responses: exports_external.record(Identity, exports_external.custom(isAssessmentResponseValue))
12137
+ }).strict()).max(1000)
12138
+ }).strict().refine((value) => new TextEncoder().encode(JSON.stringify(value)).length <= 256000, {
12139
+ message: "Assessment transcript exceeds 256 KB"
12140
+ });
12141
+ AssessmentTranscriptError = class AssessmentTranscriptError extends Error {
12142
+ index;
12143
+ code;
12144
+ constructor(message, index = 0, code = "ASSESSMENT_FLOW_VIOLATION") {
12145
+ super(message);
12146
+ this.name = "AssessmentTranscriptError";
12147
+ this.index = index;
12148
+ this.code = code;
12149
+ }
12150
+ };
12180
12151
  ZERO_WIDTH_CHARACTERS = /[\u200B\uFEFF\u200C\u200D]/g;
12181
12152
  CURLY_SINGLE_QUOTES = /[\u2018\u2019\u201A\u201B]/g;
12182
12153
  CURLY_DOUBLE_QUOTES = /[\u201C\u201D\u201E\u201F]/g;
@@ -12268,6 +12239,10 @@ var init_assessment_runtime2 = __esm(() => {
12268
12239
  bankRevision: IdentitySchema,
12269
12240
  standard: AssessmentStandardRefSchema
12270
12241
  }).strict();
12242
+ FixedChildResultContextV1Schema = exports_external.object({
12243
+ kind: exports_external.literal("fixed"),
12244
+ purpose: exports_external.enum(["mastery", "end_of_course", "diagnostic"])
12245
+ }).strict();
12271
12246
  DiagnosticChildResultContextV1Schema = exports_external.object({
12272
12247
  kind: exports_external.literal("platform-routed-diagnostic"),
12273
12248
  definitionId: IdentitySchema,
@@ -12287,6 +12262,11 @@ var init_assessment_runtime2 = __esm(() => {
12287
12262
  isCorrect: exports_external.boolean().optional(),
12288
12263
  context: ReviewChildResultContextV1Schema
12289
12264
  }).strict();
12265
+ FixedChildResultPayloadV1Schema = exports_external.object({
12266
+ ...CommonPayloadShape,
12267
+ isCorrect: exports_external.boolean().optional(),
12268
+ context: FixedChildResultContextV1Schema
12269
+ }).strict();
12290
12270
  DiagnosticChildResultPayloadV1Schema = exports_external.object({
12291
12271
  ...CommonPayloadShape,
12292
12272
  isCorrect: exports_external.boolean(),
@@ -12302,7 +12282,8 @@ var init_assessment_runtime2 = __esm(() => {
12302
12282
  });
12303
12283
  AssessmentChildResultPayloadV1Schema = exports_external.union([
12304
12284
  ReviewChildResultPayloadV1Schema,
12305
- DiagnosticChildResultPayloadV1Schema
12285
+ DiagnosticChildResultPayloadV1Schema,
12286
+ FixedChildResultPayloadV1Schema
12306
12287
  ]);
12307
12288
  AssessmentChildResultCommandV1Schema = exports_external.object({
12308
12289
  kind: exports_external.literal(ASSESSMENT_CHILD_RESULT_KIND),
@@ -12379,9 +12360,19 @@ var init_assessment_runtime2 = __esm(() => {
12379
12360
  StartAssessmentRequestBodySchema = exports_external.intersection(StartAssessmentBodySchema, exports_external.object({ preparationReceipt: AssessmentPreparationReceiptSchema.optional() }));
12380
12361
  SaveAssessmentBodySchema = exports_external.object({
12381
12362
  expectedResponseVersion: exports_external.number().int().nonnegative(),
12382
- responses: exports_external.record(ResponseKeySchema2, exports_external.record(ResponseKeySchema2, AssessmentResponseValueSchema2.nullable()))
12363
+ transcript: AssessmentTranscriptSchema
12364
+ });
12365
+ RecordAssessmentProgressBodySchema = SaveAssessmentBodySchema.extend({
12366
+ assessmentToken: exports_external.string().min(1).max(32000),
12367
+ itemIdentifier: ResponseKeySchema2
12383
12368
  });
12369
+ ResumeAssessmentBodySchema = exports_external.object({
12370
+ transcript: AssessmentTranscriptSchema.optional()
12371
+ });
12372
+ ResumeRuntimeAssessmentRequestSchema = AssessmentRuntimeIdentitySchema.extend(ResumeAssessmentBodySchema.shape);
12384
12373
  SubmitAssessmentItemBodySchema = exports_external.object({
12374
+ assessmentToken: exports_external.string().min(1).max(32000),
12375
+ transcript: AssessmentTranscriptSchema,
12385
12376
  expectedResponseVersion: exports_external.number().int().nonnegative(),
12386
12377
  submissionId: exports_external.string().trim().min(1),
12387
12378
  itemIdentifier: ResponseKeySchema2,
@@ -12389,6 +12380,7 @@ var init_assessment_runtime2 = __esm(() => {
12389
12380
  responses: exports_external.record(ResponseKeySchema2, AssessmentResponseValueSchema2.nullable())
12390
12381
  });
12391
12382
  SubmitAssessmentBodySchema = exports_external.object({
12383
+ transcript: AssessmentTranscriptSchema,
12392
12384
  expectedResponseVersion: exports_external.number().int().nonnegative(),
12393
12385
  submissionId: exports_external.string().trim().min(1),
12394
12386
  session: exports_external.custom(isAssessmentSessionTiming, {
@@ -12407,6 +12399,7 @@ var init_assessment_runtime2 = __esm(() => {
12407
12399
  PrepareRuntimeAssessmentRequestSchema = AssessmentRuntimeIdentitySchema.and(StartAssessmentBodySchema);
12408
12400
  StartRuntimeAssessmentRequestSchema = AssessmentRuntimeIdentitySchema.and(StartAssessmentRequestBodySchema);
12409
12401
  SaveRuntimeAssessmentRequestSchema = AssessmentRuntimeIdentitySchema.extend(SaveAssessmentBodySchema.shape);
12402
+ RecordRuntimeAssessmentProgressRequestSchema = AssessmentRuntimeIdentitySchema.extend(RecordAssessmentProgressBodySchema.shape);
12410
12403
  SubmitAssessmentItemRuntimeRequestSchema = AssessmentRuntimeIdentitySchema.extend(SubmitAssessmentItemBodySchema.shape);
12411
12404
  SubmitRuntimeAssessmentRequestSchema = AssessmentRuntimeIdentitySchema.extend(SubmitAssessmentBodySchema.shape).extend({
12412
12405
  appName: exports_external.string().trim().min(1),
@@ -100206,22 +100199,6 @@ var init_locks = __esm(() => {
100206
100199
  });
100207
100200
 
100208
100201
  // ../api-core/src/utils/assessment-runtime-lock.util.ts
100209
- async function crossAssessmentAttemptLockBarrier(lock, db2, attemptId) {
100210
- const maxAttempts = 5;
100211
- for (let attempt = 1;attempt <= maxAttempts; attempt += 1) {
100212
- try {
100213
- await lock(db2, [assessmentRuntimeAttemptLockKey(attemptId)], async () => {
100214
- return;
100215
- });
100216
- return;
100217
- } catch (error88) {
100218
- if (!(error88 instanceof ServiceUnavailableError) || attempt === maxAttempts) {
100219
- throw error88;
100220
- }
100221
- await sleep(25);
100222
- }
100223
- }
100224
- }
100225
100202
  function normalizeKeys(keys) {
100226
100203
  return [...new Set(keys)].toSorted();
100227
100204
  }
@@ -100294,17 +100271,6 @@ var init_assessment_runtime_lock_util = __esm(() => {
100294
100271
  });
100295
100272
 
100296
100273
  // ../api-core/src/utils/timeback-assessment-runtime.util.ts
100297
- function assessmentPreparationContentionError(expectedResponseVersion, responseVersion, kind) {
100298
- if (expectedResponseVersion === responseVersion) {
100299
- return new ServiceUnavailableError(`The ${kind} response state changed repeatedly while this item was being prepared. Try again shortly.`, {
100300
- retryable: true,
100301
- reason: "PREPARATION_CONTENTION",
100302
- expectedResponseVersion,
100303
- responseVersion
100304
- });
100305
- }
100306
- return AssessmentRuntimeError.from(assessmentResponseVersionConflict(expectedResponseVersion, responseVersion));
100307
- }
100308
100274
  function assessmentTrackableCourse(integration) {
100309
100275
  if (!isTimebackGrade2(integration.grade) || !isTimebackSubject(integration.subject)) {
100310
100276
  return null;
@@ -100370,7 +100336,7 @@ function buildConventionalAssessmentSnapshot(input) {
100370
100336
  ...input.activityData ? { activityData: input.activityData } : {},
100371
100337
  assessment: assessmentPresentationForAttempt(input.assessment, input.attemptId),
100372
100338
  responses: metadata2.responses,
100373
- itemSubmissions: metadata2.itemSubmissions,
100339
+ itemSubmissions: assessmentFlowForPurpose(metadata2.purpose) === "item-submit" ? metadata2.itemSubmissions : [],
100374
100340
  score: null,
100375
100341
  selection
100376
100342
  };
@@ -100420,151 +100386,9 @@ async function resolveAvailableAssessmentAttempt(initialDecision, select3, resum
100420
100386
  }
100421
100387
  return { kind: "continue", decision };
100422
100388
  }
100423
- function buildReviewItemResultMetadata(input) {
100424
- const administration = itemAdministration(input.metadata.itemSubmissions, input.selection.itemIdentifier);
100425
- if (!administration) {
100426
- throw new Error(`Cannot create a review result for unadministered candidate ${input.selection.itemIdentifier}`);
100427
- }
100428
- return {
100429
- version: PLAYCADEMY_ASSESSMENT_ITEM_RESULT_METADATA_VERSION,
100430
- parentAttemptId: input.parentAttemptId,
100431
- activityId: input.metadata.activityId,
100432
- integrationId: input.metadata.integrationId,
100433
- enrollmentId: input.metadata.enrollmentId,
100434
- bankRevision: input.metadata.review.bankRevision,
100435
- itemIdentifier: input.selection.itemIdentifier,
100436
- standard: input.selection.standard,
100437
- deliveredAt: administration.submittedAt,
100438
- responseVersion: input.responseVersion ?? input.metadata.responseVersion,
100439
- responses: input.metadata.responses[input.selection.itemIdentifier] ?? {},
100440
- ...input.submissionId ? { submissionId: input.submissionId } : {},
100441
- ...input.isCorrect === undefined ? {} : { isCorrect: input.isCorrect }
100442
- };
100443
- }
100444
- function buildOpenReviewChildResult(input) {
100445
- return {
100446
- status: "active",
100447
- assessmentLineItem: { sourcedId: input.childLineItemId },
100448
- student: input.student,
100449
- score: 0,
100450
- scoreDate: input.metadata.deliveredAt,
100451
- ...ASSESSMENT_ATTEMPT_OPEN,
100452
- metadata: {
100453
- [PLAYCADEMY_ASSESSMENT_ITEM_RESULT_METADATA_KEY]: input.metadata
100454
- }
100455
- };
100456
- }
100457
100389
  function diagnosticQuestionLineItemId(parentLineItemId, itemIdentifier) {
100458
100390
  return deterministicUUID(["playcademy:diagnostic-question-line-item:v1", parentLineItemId, itemIdentifier].join("\x00"));
100459
100391
  }
100460
- function diagnosticQuestionResultId(parentAttemptId, childLineItemId) {
100461
- return deterministicUUID(["playcademy:diagnostic-question-result:v1", parentAttemptId, childLineItemId].join("\x00"));
100462
- }
100463
- function diagnosticAdministration(input) {
100464
- const ledgerIndex = input.metadata.diagnostic.ledger.findIndex((entry) => entry.routingNodeKey === input.ledgerEntry.routingNodeKey);
100465
- const persistedEntry = input.metadata.diagnostic.ledger[ledgerIndex];
100466
- const administration = input.metadata.itemSubmissions[ledgerIndex];
100467
- if (ledgerIndex === -1 || !persistedEntry || !administration || persistedEntry.stageKey !== input.ledgerEntry.stageKey || persistedEntry.trackKey !== input.ledgerEntry.trackKey || persistedEntry.itemIdentifier !== input.ledgerEntry.itemIdentifier || persistedEntry.isCorrect !== input.ledgerEntry.isCorrect || administration.itemIdentifier !== input.ledgerEntry.itemIdentifier || administration.isCorrect !== input.ledgerEntry.isCorrect) {
100468
- throw new Error(`Cannot create a diagnostic result for uncommitted routing node ${input.ledgerEntry.routingNodeKey}`);
100469
- }
100470
- return administration;
100471
- }
100472
- function buildDiagnosticItemResultMetadata(input) {
100473
- const administration = diagnosticAdministration(input);
100474
- return {
100475
- version: PLAYCADEMY_ASSESSMENT_ITEM_RESULT_METADATA_VERSION,
100476
- parentAttemptId: input.parentAttemptId,
100477
- activityId: input.metadata.activityId,
100478
- integrationId: input.metadata.integrationId,
100479
- enrollmentId: input.metadata.enrollmentId,
100480
- definitionId: input.metadata.diagnostic.definitionId,
100481
- diagnosticKey: input.metadata.diagnostic.diagnosticKey,
100482
- routingRevision: input.metadata.diagnostic.routingRevision,
100483
- contentRevision: input.metadata.selectedTest.contentRevision,
100484
- stageKey: input.ledgerEntry.stageKey,
100485
- trackKey: input.ledgerEntry.trackKey,
100486
- routingNodeKey: input.ledgerEntry.routingNodeKey,
100487
- itemIdentifier: input.ledgerEntry.itemIdentifier,
100488
- deliveredAt: administration.submittedAt,
100489
- responseVersion: administration.responseVersion,
100490
- responses: input.metadata.responses[input.ledgerEntry.itemIdentifier] ?? {},
100491
- submissionId: administration.submissionId,
100492
- isCorrect: input.ledgerEntry.isCorrect
100493
- };
100494
- }
100495
- function resolveDiagnosticChildResult(input) {
100496
- if (!input.childResult) {
100497
- return { action: "create" };
100498
- }
100499
- const childMetadata = playcademyDiagnosticAssessmentItemResultMetadata(input.childResult.metadata);
100500
- const expectedMetadata = buildDiagnosticItemResultMetadata({
100501
- metadata: input.parentMetadata,
100502
- ledgerEntry: input.ledgerEntry,
100503
- parentAttemptId: input.parentResult.sourcedId
100504
- });
100505
- if (input.childResult.assessmentLineItem.sourcedId !== input.childLineItemId || input.childResult.student.sourcedId !== input.parentResult.student.sourcedId || !childMetadata || canonicalJson2(childMetadata) !== canonicalJson2(expectedMetadata)) {
100506
- return { action: "restore", reason: "incompatible" };
100507
- }
100508
- return input.childResult.status === "active" ? { action: "reuse", metadata: expectedMetadata } : { action: "restore", reason: "inactive" };
100509
- }
100510
- function buildFinalizedDiagnosticChildResult(input) {
100511
- const administration = diagnosticAdministration(input);
100512
- const metadata2 = buildDiagnosticItemResultMetadata(input);
100513
- return {
100514
- resultUpdate: {
100515
- status: "active",
100516
- assessmentLineItem: { sourcedId: input.childLineItemId },
100517
- student: input.student,
100518
- score: administration.score.earned,
100519
- scoreDate: administration.submittedAt,
100520
- ...ASSESSMENT_ATTEMPT_COMPLETED,
100521
- metadata: {
100522
- [PLAYCADEMY_ASSESSMENT_ITEM_RESULT_METADATA_KEY]: metadata2
100523
- }
100524
- }
100525
- };
100526
- }
100527
- function buildFinalizedReviewChildResult(input) {
100528
- const metadata2 = buildReviewItemResultMetadata({
100529
- metadata: input.metadata,
100530
- selection: input.selection,
100531
- parentAttemptId: input.parentAttemptId,
100532
- submissionId: input.submissionId,
100533
- isCorrect: input.itemResult.isCorrect
100534
- });
100535
- return {
100536
- resultUpdate: {
100537
- status: "active",
100538
- assessmentLineItem: { sourcedId: input.childLineItemId },
100539
- student: input.student,
100540
- score: input.itemResult.score.earned,
100541
- scoreDate: input.timestamp,
100542
- ...ASSESSMENT_ATTEMPT_COMPLETED,
100543
- metadata: {
100544
- [PLAYCADEMY_ASSESSMENT_ITEM_RESULT_METADATA_KEY]: metadata2
100545
- }
100546
- },
100547
- outcome: {
100548
- itemIdentifier: input.selection.itemIdentifier,
100549
- standard: input.selection.standard,
100550
- answered: Object.keys(input.metadata.responses[input.selection.itemIdentifier] ?? {}).length > 0,
100551
- score: input.itemResult.score,
100552
- isCorrect: input.itemResult.isCorrect
100553
- }
100554
- };
100555
- }
100556
- function resolveReviewChildResult(input) {
100557
- if (!input.childResult) {
100558
- return { action: "create" };
100559
- }
100560
- const childMetadata = playcademyAssessmentItemResultMetadata(input.childResult.metadata);
100561
- const expectedResponses = input.parentMetadata.responses[input.selection.itemIdentifier] ?? {};
100562
- const administration = itemAdministration(input.parentMetadata.itemSubmissions, input.selection.itemIdentifier);
100563
- if (input.childResult.assessmentLineItem.sourcedId !== input.childLineItemId || input.childResult.student.sourcedId !== input.parentResult.student.sourcedId || !childMetadata || childMetadata.parentAttemptId !== input.parentResult.sourcedId || childMetadata.activityId !== input.parentMetadata.activityId || childMetadata.integrationId !== input.parentMetadata.integrationId || childMetadata.enrollmentId !== input.parentMetadata.enrollmentId || childMetadata.bankRevision !== input.parentMetadata.review.bankRevision || childMetadata.itemIdentifier !== input.selection.itemIdentifier || childMetadata.standard.framework !== input.selection.standard.framework || childMetadata.standard.identifier !== input.selection.standard.identifier || childMetadata.deliveredAt !== administration?.submittedAt || childMetadata.responseVersion > input.parentMetadata.responseVersion || canonicalJson2(childMetadata.responses) !== canonicalJson2(expectedResponses)) {
100564
- return { action: "restore", reason: "incompatible" };
100565
- }
100566
- return input.childResult.status === "active" ? { action: "reuse", metadata: childMetadata } : { action: "restore", reason: "inactive" };
100567
- }
100568
100392
  function buildAssessmentSubmitResult(input) {
100569
100393
  if (!input.metadata.score || !input.metadata.submissionId) {
100570
100394
  return null;
@@ -100950,32 +100774,12 @@ function assessmentFixtureScoringKeys(assessment, questions) {
100950
100774
  ...Object.keys(responseAreas).length > 0 ? { responseAreas } : {}
100951
100775
  };
100952
100776
  }
100953
- function prepareDiagnosticAssessmentResponses(assessment, current, input) {
100954
- const update2 = { [input.itemIdentifier]: input.responses };
100955
- validateAssessmentResponseUpdate(assessment, update2);
100956
- const responses = applyAssessmentResponseUpdate(current, update2);
100957
- const itemResponses = responses[input.itemIdentifier];
100958
- validateAssessmentDraftResponses(assessment, itemResponses ? { [input.itemIdentifier]: itemResponses } : {});
100959
- return responses;
100960
- }
100961
100777
  function validateAssessmentResponseUpdate(assessment, update2) {
100962
100778
  const message = assessmentResponseUpdateValidationMessage(assessment, update2);
100963
100779
  if (message) {
100964
100780
  throw new AssessmentRuntimeError("INVALID_RESPONSE", message);
100965
100781
  }
100966
100782
  }
100967
- function validateAssessmentResponses(assessment, responses) {
100968
- const message = assessmentResponseValidationMessage(assessment, responses);
100969
- if (message) {
100970
- throw new AssessmentRuntimeError("INVALID_RESPONSE", message);
100971
- }
100972
- }
100973
- function validateAssessmentDraftResponses(assessment, responses) {
100974
- const message = assessmentDraftResponseValidationMessage(assessment, responses);
100975
- if (message) {
100976
- throw new AssessmentRuntimeError("INVALID_RESPONSE", message);
100977
- }
100978
- }
100979
100783
  function assessmentScoringRequests(assessment, responses) {
100980
100784
  return assessment.items.map((item) => ({
100981
100785
  itemIdentifier: item.identifier,
@@ -101015,6 +100819,21 @@ function assessmentScoreFromPercentage(percentage, possible) {
101015
100819
  const earned = Math.round(normalized * possible * 1e6) / 1e6;
101016
100820
  return { earned, possible, normalized };
101017
100821
  }
100822
+ function buildAssessmentResultCheckpoint(input) {
100823
+ return {
100824
+ status: input.result.status,
100825
+ assessmentLineItem: input.result.assessmentLineItem,
100826
+ student: input.result.student,
100827
+ score: input.result.score ?? 0,
100828
+ scoreDate: input.result.scoreDate,
100829
+ ...ASSESSMENT_ATTEMPT_OPEN,
100830
+ metadata: mergeAssessmentRuntimeMetadata(input.result.metadata, {
100831
+ ...input.metadata,
100832
+ transcript: input.transcript,
100833
+ updatedAt: input.timestamp
100834
+ })
100835
+ };
100836
+ }
101018
100837
  function buildAssessmentResultSubmission(input) {
101019
100838
  const metadata2 = {
101020
100839
  ...input.metadata,
@@ -102419,6 +102238,183 @@ var init_timeback_assessment_artifacts_util = __esm(() => {
102419
102238
  init_timeback_review_mapping_util();
102420
102239
  });
102421
102240
 
102241
+ // ../api-core/src/utils/timeback-assessment-scoring.util.ts
102242
+ async function scoreCachedAssessmentItems(input) {
102243
+ const selected = input.assessment.items.filter((item) => input.itemIdentifiers.has(item.identifier));
102244
+ const keys = new Map(await Promise.all(selected.map(async (item) => [
102245
+ item.identifier,
102246
+ await sha256Hex(canonicalJson2({
102247
+ identity: input.artifactIdentity,
102248
+ purpose: input.purpose,
102249
+ itemIdentifier: item.identifier,
102250
+ responses: input.responses[item.identifier] ?? {},
102251
+ strategy: input.artifact?.items.find((candidate) => candidate.itemIdentifier === item.identifier)?.strategy ?? "qti"
102252
+ }))
102253
+ ])));
102254
+ const scored = new Map;
102255
+ for (const item of selected) {
102256
+ const cached3 = input.cache.get(keys.get(item.identifier));
102257
+ if (cached3) {
102258
+ scored.set(item.identifier, cached3);
102259
+ }
102260
+ }
102261
+ const missing = new Set(selected.filter((item) => !scored.has(item.identifier)).map((item) => item.identifier));
102262
+ const fresh = await scoreAssessmentItems({ ...input, itemIdentifiers: missing });
102263
+ for (const result of fresh) {
102264
+ input.cache.set(keys.get(result.itemIdentifier), result);
102265
+ scored.set(result.itemIdentifier, result);
102266
+ }
102267
+ return selected.map((item) => scored.get(item.identifier));
102268
+ }
102269
+ async function scoreAssessmentItems({
102270
+ assessment,
102271
+ responses,
102272
+ artifactIdentity,
102273
+ purpose,
102274
+ itemIdentifiers,
102275
+ artifact,
102276
+ processResponse,
102277
+ onEvent
102278
+ }) {
102279
+ const scoringItems = assessmentScoringRequests(assessment, responses).filter((item) => !itemIdentifiers || itemIdentifiers.has(item.itemIdentifier));
102280
+ const artifactItems = new Map(artifact?.items.map((item) => [item.itemIdentifier, item]));
102281
+ const scored = new Map;
102282
+ const qti2 = [];
102283
+ for (const item of scoringItems) {
102284
+ const artifactItem = artifactItems.get(item.itemIdentifier);
102285
+ let fallback2 = null;
102286
+ if (!artifact) {
102287
+ fallback2 = { reason: "artifact-unavailable" };
102288
+ } else if (!artifactItem) {
102289
+ fallback2 = { reason: "artifact-item-missing" };
102290
+ } else if (artifactItem.strategy === "qti") {
102291
+ fallback2 = { reason: "artifact-qti", artifactReason: artifactItem.reason };
102292
+ } else {
102293
+ const startedAt = Date.now();
102294
+ try {
102295
+ scored.set(item.itemIdentifier, {
102296
+ ...scorePlatformAssessmentItem(artifactItem, responses[item.itemIdentifier]),
102297
+ grading: PLATFORM_CHILD_GRADING
102298
+ });
102299
+ onEvent("assessment.item_scoring", {
102300
+ "app.assessment.qti_item_identifier": item.itemIdentifier,
102301
+ "app.assessment.purpose": purpose,
102302
+ "app.assessment.artifact_identity_kind": artifactIdentity.kind,
102303
+ "app.assessment.grading_source": "platform-artifact",
102304
+ "app.assessment.grading_duration_ms": Date.now() - startedAt
102305
+ });
102306
+ } catch (error88) {
102307
+ fallback2 = { reason: "platform-grader-error", error: error88 };
102308
+ }
102309
+ }
102310
+ if (fallback2) {
102311
+ qti2.push({ item, ...fallback2 });
102312
+ }
102313
+ }
102314
+ if (qti2.length > 0) {
102315
+ const fallbackTasks = qti2.flatMap(({ item, reason, artifactReason, error: graderError }) => {
102316
+ const interactionResults = [];
102317
+ let startedAt;
102318
+ let completedInteractions = 0;
102319
+ let failed = false;
102320
+ const omittedResponseCount = item.requests.filter((request) => responses[item.itemIdentifier]?.[request.identifier] === undefined).length;
102321
+ let qtiRemoteInteractionCount = 0;
102322
+ let syntheticIncorrectInteractionCount = 0;
102323
+ function fallbackEvent(outcome) {
102324
+ onEvent("assessment.item_scoring_fallback", {
102325
+ "app.assessment.qti_item_identifier": item.itemIdentifier,
102326
+ "app.assessment.purpose": purpose,
102327
+ "app.assessment.artifact_identity_kind": artifactIdentity.kind,
102328
+ "app.assessment.grading_source": omittedResponseCount > 0 ? "platform-qti-adapter" : "timeback-qti",
102329
+ "app.assessment.fallback_reason": reason,
102330
+ "app.assessment.fallback_duration_ms": startedAt === undefined ? 0 : Date.now() - startedAt,
102331
+ "app.assessment.qti_remote_interaction_count": qtiRemoteInteractionCount,
102332
+ "app.assessment.synthetic_incorrect_interaction_count": syntheticIncorrectInteractionCount,
102333
+ ...artifactReason ? { "app.assessment.artifact_fallback_reason": artifactReason } : {},
102334
+ ...graderError ? {
102335
+ "app.assessment.platform_grader_exception_type": errorType(graderError)
102336
+ } : {},
102337
+ ...outcome
102338
+ });
102339
+ }
102340
+ function completeItem() {
102341
+ const earned = assessmentItemEarnedScore(interactionResults.map((result) => Number.isFinite(result.score) ? result.score : 0), item.maxScore);
102342
+ const possible = item.maxScore;
102343
+ fallbackEvent({ "app.assessment.fallback_outcome": "succeeded" });
102344
+ scored.set(item.itemIdentifier, {
102345
+ itemIdentifier: item.itemIdentifier,
102346
+ score: {
102347
+ earned,
102348
+ possible,
102349
+ normalized: possible === 0 ? 0 : earned / possible
102350
+ },
102351
+ isCorrect: assessmentItemCorrectness(interactionResults.map((result) => result.isCorrect)),
102352
+ grading: omittedResponseCount > 0 ? PLATFORM_QTI_ADAPTER_CHILD_GRADING : QTI_CHILD_GRADING
102353
+ });
102354
+ }
102355
+ if (item.requests.length === 0) {
102356
+ completeItem();
102357
+ return [];
102358
+ }
102359
+ return item.requests.map((request, index2) => async () => {
102360
+ startedAt ??= Date.now();
102361
+ try {
102362
+ const response = responses[item.itemIdentifier]?.[request.identifier];
102363
+ if (response === undefined) {
102364
+ syntheticIncorrectInteractionCount += 1;
102365
+ interactionResults[index2] = { score: 0, isCorrect: false };
102366
+ } else {
102367
+ qtiRemoteInteractionCount += 1;
102368
+ interactionResults[index2] = await processResponse(item.itemIdentifier, request);
102369
+ }
102370
+ completedInteractions += 1;
102371
+ if (completedInteractions === item.requests.length && !failed) {
102372
+ completeItem();
102373
+ }
102374
+ } catch (error88) {
102375
+ if (!failed) {
102376
+ failed = true;
102377
+ fallbackEvent({
102378
+ "app.assessment.fallback_outcome": "failed",
102379
+ "exception.type": errorType(error88),
102380
+ "app.error.message": errorMessage(error88)
102381
+ });
102382
+ }
102383
+ throw error88;
102384
+ }
102385
+ });
102386
+ });
102387
+ await runWithConcurrency(fallbackTasks, SCORING_CONCURRENCY, (task) => task());
102388
+ }
102389
+ return scoringItems.map((item) => {
102390
+ const result = scored.get(item.itemIdentifier);
102391
+ if (!result) {
102392
+ throw new Error(`Assessment item ${item.itemIdentifier} was not scored`);
102393
+ }
102394
+ return result;
102395
+ });
102396
+ }
102397
+ var QTI_CHILD_GRADING, PLATFORM_QTI_ADAPTER_CHILD_GRADING, PLATFORM_CHILD_GRADING, SCORING_CONCURRENCY = 4;
102398
+ var init_timeback_assessment_scoring_util = __esm(() => {
102399
+ init_assessment_runtime2();
102400
+ init_assessment_artifact_envelope_util();
102401
+ init_timeback_assessment_runtime_util();
102402
+ QTI_CHILD_GRADING = {
102403
+ source: "timeback-qti",
102404
+ graderVersion: "timeback-qti-process-response-v1"
102405
+ };
102406
+ PLATFORM_QTI_ADAPTER_CHILD_GRADING = {
102407
+ source: "platform-qti-adapter",
102408
+ graderVersion: "platform-qti-adapter-v1",
102409
+ qtiGraderVersion: "timeback-qti-process-response-v1"
102410
+ };
102411
+ PLATFORM_CHILD_GRADING = {
102412
+ source: "platform-artifact",
102413
+ artifactVersion: `assessment-artifact-v${ASSESSMENT_ARTIFACT_SCHEMA_VERSION}`,
102414
+ graderVersion: `platform-grader-v${ASSESSMENT_GRADER_PROTOCOL_VERSION}`
102415
+ };
102416
+ });
102417
+
102422
102418
  // ../api-core/src/utils/timeback-assessment-child-result.util.ts
102423
102419
  async function buildSubmissionChildResultCommand(input) {
102424
102420
  const { submission, context: context2 } = input;
@@ -102434,12 +102430,9 @@ async function buildSubmissionChildResultCommand(input) {
102434
102430
  score: submission.score,
102435
102431
  grading: input.grading
102436
102432
  };
102437
- if (context2.kind === "review") {
102438
- return buildAssessmentChildResultCommand({
102439
- ...common2,
102440
- ...typeof isCorrect === "boolean" ? { isCorrect } : {},
102441
- context: context2
102442
- }, submission.submittedAt);
102433
+ if (context2.kind !== "platform-routed-diagnostic") {
102434
+ const payload = { ...common2, ...typeof isCorrect === "boolean" ? { isCorrect } : {} };
102435
+ return context2.kind === "review" ? buildAssessmentChildResultCommand({ ...payload, context: context2 }, submission.submittedAt) : buildAssessmentChildResultCommand({ ...payload, context: context2 }, submission.submittedAt);
102443
102436
  }
102444
102437
  if (typeof isCorrect !== "boolean") {
102445
102438
  throw new Error("Diagnostic child results require determinate correctness");
@@ -102460,11 +102453,7 @@ function assessmentChildLineItemMetadataMatches(key, stored, expected) {
102460
102453
  if (!Array.isArray(storedStandards) || !Array.isArray(expectedStandards)) {
102461
102454
  return false;
102462
102455
  }
102463
- const sameDefinition = canonicalJson2(reviewDefinitionWithoutStandards(storedDefinition)) === canonicalJson2(reviewDefinitionWithoutStandards(expectedDefinition));
102464
- function contains(standards, candidate) {
102465
- return standards.some((standard) => canonicalJson2(standard) === canonicalJson2(candidate));
102466
- }
102467
- return sameDefinition && (expectedStandards.every((standard) => contains(storedStandards, standard)) || storedStandards.every((standard) => contains(expectedStandards, standard)));
102456
+ return canonicalJson2(reviewDefinitionWithoutStandards(storedDefinition)) === canonicalJson2(reviewDefinitionWithoutStandards(expectedDefinition));
102468
102457
  }
102469
102458
  function assessmentChildLineItemMatches(stored, expected) {
102470
102459
  if (stored.status !== expected.status || stored.parentAssessmentLineItem?.sourcedId !== expected.parentAssessmentLineItem.sourcedId || stored.course?.sourcedId !== expected.course.sourcedId || stored.resultValueMin !== expected.resultValueMin || stored.resultValueMax !== expected.resultValueMax || typeof stored.metadata !== "object" || stored.metadata === null) {
@@ -102522,19 +102511,14 @@ async function classifyAssessmentChildResult(input) {
102522
102511
  result.scoreScale,
102523
102512
  result.scorePercentile,
102524
102513
  result.comment,
102525
- result.learningObjectiveSet,
102526
- result.incomplete,
102527
- result.late,
102528
- result.missing
102514
+ result.learningObjectiveSet
102529
102515
  ];
102530
- if (auxiliaryFields.some((value) => value !== undefined && value !== null)) {
102516
+ const flags2 = [result.incomplete, result.late, result.missing];
102517
+ if (auxiliaryFields.some((value) => value !== undefined && value !== null) || flags2.some((value) => value !== undefined && value !== null && value !== "false")) {
102531
102518
  return { outcome: "conflict", reason: "auxiliary-field-mismatch" };
102532
102519
  }
102533
102520
  return { outcome: "replay" };
102534
102521
  }
102535
- function hasApiStatus(error88, statusCode) {
102536
- return isApiError(error88) && error88.statusCode === statusCode;
102537
- }
102538
102522
  async function persistAssessmentChildResult(input) {
102539
102523
  const { assessmentResults, childLineItemId } = input;
102540
102524
  const verification2 = await verifyAssessmentChildResultCommand(input.command);
@@ -102543,38 +102527,127 @@ async function persistAssessmentChildResult(input) {
102543
102527
  }
102544
102528
  const { command } = verification2;
102545
102529
  const resultId = await assessmentChildResultId(command.payload.attempt.attemptId, command.payload.administration.submissionId);
102546
- async function classifyOccupant() {
102547
- const resolution = await classifyAssessmentChildResult({
102548
- result: await assessmentResults.get(resultId),
102530
+ await assessmentResults.upsert(resultId, buildAssessmentChildResultUpdate(command, childLineItemId));
102531
+ }
102532
+ function assessmentTranscriptChildLineItemId(context2, itemIdentifier) {
102533
+ return context2.metadata.purpose === "review" ? reviewQuestionLineItemId(context2.parentLineItemId, itemIdentifier) : diagnosticQuestionLineItemId(context2.parentLineItemId, itemIdentifier);
102534
+ }
102535
+ async function reconcileAssessmentTranscriptChildren({
102536
+ context: context2,
102537
+ graded,
102538
+ only,
102539
+ assessmentResults,
102540
+ ensureLineItems,
102541
+ onPersisted
102542
+ }) {
102543
+ const metadata2 = graded.metadata;
102544
+ const assessmentKey = metadata2.selectedTest.assessmentKey;
102545
+ if (!assessmentKey) {
102546
+ throw new Error("Assessment context requires a selected assessment key");
102547
+ }
102548
+ const submissions = metadata2.itemSubmissions.filter((submission) => !only || only.has(submission.submissionId));
102549
+ const administered = new Set(submissions.map((submission) => submission.itemIdentifier));
102550
+ await ensureLineItems({
102551
+ items: graded.assessment.items.filter((item) => administered.has(item.identifier))
102552
+ });
102553
+ await runWithConcurrency(submissions, 4, async (submission) => {
102554
+ const index2 = submission.responseVersion - 1;
102555
+ let flow;
102556
+ if (metadata2.purpose === "review") {
102557
+ const selection = metadata2.review.selections.find((item) => item.itemIdentifier === submission.itemIdentifier);
102558
+ flow = {
102559
+ kind: "review",
102560
+ bankRevision: metadata2.review.bankRevision,
102561
+ standard: selection.standard
102562
+ };
102563
+ } else if (metadata2.purpose === "diagnostic" && metadata2.diagnostic) {
102564
+ flow = {
102565
+ kind: "platform-routed-diagnostic",
102566
+ definitionId: metadata2.diagnostic.definitionId,
102567
+ diagnosticKey: metadata2.diagnostic.diagnosticKey,
102568
+ routingRevision: metadata2.diagnostic.routingRevision,
102569
+ transition: metadata2.diagnostic.ledger[index2]
102570
+ };
102571
+ } else {
102572
+ flow = { kind: "fixed", purpose: metadata2.purpose };
102573
+ }
102574
+ const command = await buildSubmissionChildResultCommand({
102575
+ attempt: {
102576
+ attemptId: context2.attemptId,
102577
+ gameId: context2.gameId,
102578
+ studentId: context2.studentId,
102579
+ activityId: metadata2.activityId,
102580
+ courseId: metadata2.courseId,
102581
+ integrationId: metadata2.integrationId,
102582
+ enrollmentId: metadata2.enrollmentId,
102583
+ selectedTest: { ...metadata2.selectedTest, assessmentKey }
102584
+ },
102585
+ submission,
102586
+ responses: graded.transcript.administrations[index2].responses,
102587
+ grading: submission.grading ?? QTI_CHILD_GRADING,
102588
+ context: flow
102589
+ });
102590
+ await persistAssessmentChildResult({
102591
+ assessmentResults,
102549
102592
  command,
102550
- childLineItemId
102593
+ childLineItemId: await assessmentTranscriptChildLineItemId(context2, submission.itemIdentifier)
102551
102594
  });
102552
- return resolution.outcome === "replay" ? { outcome: "replay", resultId } : { outcome: "conflict", resultId, reason: resolution.reason };
102595
+ onPersisted?.(submission.submissionId);
102596
+ });
102597
+ }
102598
+ async function recoveredAssessmentAdministration(context2, result) {
102599
+ const verified = await verifyAssessmentChildResultCommand(result.metadata);
102600
+ if (!verified.ok) {
102601
+ return null;
102553
102602
  }
102554
- try {
102555
- return await classifyOccupant();
102556
- } catch (error88) {
102557
- if (!hasApiStatus(error88, 404)) {
102558
- throw error88;
102603
+ const { command } = verified;
102604
+ const { attempt, administration, responses, context: flow } = command.payload;
102605
+ if (attempt.attemptId !== context2.attemptId || attempt.gameId !== context2.gameId || attempt.studentId !== context2.studentId || attempt.integrationId !== context2.metadata.integrationId || attempt.enrollmentId !== context2.metadata.enrollmentId || attempt.courseId !== context2.metadata.courseId || attempt.activityId !== context2.metadata.activityId || canonicalJson2(attempt.selectedTest) !== canonicalJson2(context2.metadata.selectedTest) || result.sourcedId !== await assessmentChildResultId(context2.attemptId, administration.submissionId)) {
102606
+ return null;
102607
+ }
102608
+ const disposition = await classifyAssessmentChildResult({
102609
+ result,
102610
+ command,
102611
+ childLineItemId: await assessmentTranscriptChildLineItemId(context2, administration.itemIdentifier)
102612
+ });
102613
+ if (disposition.outcome !== "replay") {
102614
+ return null;
102615
+ }
102616
+ return {
102617
+ sequence: administration.responseVersion,
102618
+ entry: {
102619
+ submissionId: administration.submissionId,
102620
+ itemIdentifier: administration.itemIdentifier,
102621
+ responses,
102622
+ ...flow.kind === "platform-routed-diagnostic" ? { routingNodeKey: flow.transition.routingNodeKey } : {}
102623
+ }
102624
+ };
102625
+ }
102626
+ async function recoverAssessmentTranscript(context2, results) {
102627
+ const entries = new Map;
102628
+ for (const result of results) {
102629
+ const recovered = await recoveredAssessmentAdministration(context2, result);
102630
+ if (recovered) {
102631
+ const atPosition = entries.get(recovered.sequence) ?? new Map;
102632
+ atPosition.set(recovered.entry.submissionId, recovered.entry);
102633
+ entries.set(recovered.sequence, atPosition);
102559
102634
  }
102560
102635
  }
102561
- try {
102562
- await assessmentResults.create({
102563
- sourcedId: resultId,
102564
- ...buildAssessmentChildResultUpdate(command, childLineItemId)
102565
- });
102566
- return { outcome: "created", resultId };
102567
- } catch (error88) {
102568
- if (!hasApiStatus(error88, 409)) {
102569
- throw error88;
102636
+ const transcript = emptyAssessmentTranscript(context2.attemptId, context2.metadata.selectedTest.contentRevision);
102637
+ for (let sequence2 = 1;sequence2 <= entries.size; sequence2 += 1) {
102638
+ const candidates = entries.get(sequence2);
102639
+ if (!candidates || candidates.size !== 1) {
102640
+ break;
102570
102641
  }
102642
+ transcript.administrations.push(candidates.values().next().value);
102571
102643
  }
102572
- return classifyOccupant();
102644
+ return transcript;
102573
102645
  }
102574
- var init_timeback_assessment_child_result_util = __esm(async () => {
102646
+ var init_timeback_assessment_child_result_util = __esm(() => {
102575
102647
  init_src();
102576
102648
  init_assessment_runtime2();
102577
- await init_errors8();
102649
+ init_timeback_assessment_runtime_util();
102650
+ init_timeback_assessment_scoring_util();
102578
102651
  });
102579
102652
 
102580
102653
  // ../api-core/src/utils/timeback-assessment-preparation.util.ts
@@ -102679,6 +102752,7 @@ function buildPreparedAssessmentMetadata(plan, timestamp6, testName) {
102679
102752
  version: PLAYCADEMY_ASSESSMENT_RESULT_METADATA_VERSION,
102680
102753
  activityId: plan.activityId,
102681
102754
  courseId: plan.courseId,
102755
+ courseName: plan.courseName,
102682
102756
  integrationId: plan.integrationId,
102683
102757
  enrollmentId: plan.enrollmentId,
102684
102758
  selectedTest: plan.selectedTest,
@@ -102709,7 +102783,6 @@ function buildPreparedAssessmentMetadata(plan, timestamp6, testName) {
102709
102783
  testName,
102710
102784
  routingRevision: plan.diagnostic.routingRevision,
102711
102785
  ledger: [],
102712
- routingSnapshots: [],
102713
102786
  state: plan.diagnostic.initialState
102714
102787
  }
102715
102788
  };
@@ -103004,127 +103077,6 @@ var init_timeback_assessment_publication_util = __esm(() => {
103004
103077
  MANAGED_ASSET_URL = new RegExp(`https?://[^\\s"')]+/(${ASSESSMENT_ASSET_KEY_PREFIX}v1/sha256/[a-f0-9]{64}\\.[a-z0-9]+)`, "gi");
103005
103078
  });
103006
103079
 
103007
- // ../api-core/src/utils/timeback-assessment-reconciliation.util.ts
103008
- function assessmentChildProjectionIsTerminal(result) {
103009
- const state = { inProgress: result.inProgress ?? "", scoreStatus: result.scoreStatus };
103010
- return isAssessmentAttemptAwaitingAward(state) || isAssessmentAttemptCompleted(state);
103011
- }
103012
- async function reconcileReviewChildResults({
103013
- initial,
103014
- writeOpen,
103015
- crossBarrier,
103016
- readLatest,
103017
- restoreTerminal
103018
- }) {
103019
- if (initial.metadata.purpose !== "review") {
103020
- throw new Error("A standards-review request selected a non-review attempt");
103021
- }
103022
- if (assessmentChildProjectionIsTerminal(initial.result)) {
103023
- const settled = { result: initial.result, metadata: initial.metadata };
103024
- await restoreTerminal(settled);
103025
- return settled;
103026
- }
103027
- await writeOpen();
103028
- await crossBarrier();
103029
- const current = await readLatest();
103030
- if (current.metadata.purpose !== "review") {
103031
- throw new Error("A standards-review request selected a non-review attempt");
103032
- }
103033
- const reviewed = { result: current.result, metadata: current.metadata };
103034
- if (assessmentChildProjectionIsTerminal(current.result)) {
103035
- await restoreTerminal(reviewed);
103036
- }
103037
- return reviewed;
103038
- }
103039
- var init_timeback_assessment_reconciliation_util = __esm(() => {
103040
- init_assessment_runtime2();
103041
- });
103042
-
103043
- // ../api-core/src/utils/timeback-assessment-replay.util.ts
103044
- function resolveDiagnosticItemReplay(metadata2, input) {
103045
- const priorIndex = metadata2.itemSubmissions.findIndex((submission) => submission.submissionId === input.submissionId);
103046
- if (priorIndex === -1) {
103047
- return { action: "missing" };
103048
- }
103049
- const prior = metadata2.itemSubmissions[priorIndex];
103050
- const ledgerEntry = metadata2.diagnostic.ledger[priorIndex];
103051
- if (!prior || !ledgerEntry || prior.itemIdentifier !== input.itemIdentifier || ledgerEntry.routingNodeKey !== input.routingNodeKey || !itemResponseUpdateMatches(metadata2.responses[input.itemIdentifier], input.responses)) {
103052
- return {
103053
- action: "conflict",
103054
- failure: assessmentFlowViolation("This diagnostic submission ID was already used for a different request.", {
103055
- submissionId: input.submissionId,
103056
- routingNodeKey: input.routingNodeKey,
103057
- itemIdentifier: input.itemIdentifier
103058
- })
103059
- };
103060
- }
103061
- return {
103062
- action: "replay",
103063
- submission: prior,
103064
- ledgerEntry,
103065
- priorIndex,
103066
- routing: metadata2.diagnostic.routingSnapshots?.[priorIndex]
103067
- };
103068
- }
103069
- function buildAssessmentItemReplayResult(attemptId, metadata2, submission) {
103070
- return {
103071
- attemptId,
103072
- responseVersion: metadata2.responseVersion,
103073
- status: "in_progress",
103074
- responses: metadata2.responses,
103075
- itemSubmissions: metadata2.itemSubmissions,
103076
- submission
103077
- };
103078
- }
103079
- function diagnosticReceipt(submission, ledgerEntry) {
103080
- return {
103081
- submissionId: submission.submissionId,
103082
- routingNodeKey: ledgerEntry.routingNodeKey,
103083
- itemIdentifier: submission.itemIdentifier,
103084
- submittedAt: submission.submittedAt,
103085
- responseVersion: submission.responseVersion,
103086
- answered: submission.answered,
103087
- score: submission.score,
103088
- isCorrect: ledgerEntry.isCorrect
103089
- };
103090
- }
103091
- function buildDiagnosticItemReplayResult(attemptId, submission, ledgerEntry, routing) {
103092
- return {
103093
- attemptId,
103094
- responseVersion: submission.responseVersion,
103095
- status: "in_progress",
103096
- routing,
103097
- submission: diagnosticReceipt(submission, ledgerEntry)
103098
- };
103099
- }
103100
- function requireAssessmentMutationAccess(attemptId, integrationStatus, enrollment) {
103101
- if (!enrollment || integrationStatus === "deactivated") {
103102
- throw AssessmentRuntimeError.from(assessmentAttemptUnauthorized(attemptId));
103103
- }
103104
- return enrollment;
103105
- }
103106
- function committedReviewItemResults(metadata2, assessment) {
103107
- const available = new Set(assessment.items.map((item) => item.identifier));
103108
- const seen = new Set;
103109
- return metadata2.itemSubmissions.map((submission) => {
103110
- if (!available.has(submission.itemIdentifier) || seen.has(submission.itemIdentifier)) {
103111
- throw AssessmentRuntimeError.from(assessmentFlowViolation("The persisted review item-submission ledger is corrupt.", {
103112
- itemIdentifier: submission.itemIdentifier
103113
- }));
103114
- }
103115
- seen.add(submission.itemIdentifier);
103116
- return {
103117
- itemIdentifier: submission.itemIdentifier,
103118
- score: submission.score,
103119
- isCorrect: submission.isCorrect
103120
- };
103121
- });
103122
- }
103123
- var init_timeback_assessment_replay_util = __esm(() => {
103124
- init_assessment_runtime2();
103125
- init_errors2();
103126
- });
103127
-
103128
103080
  // ../api-core/src/utils/timeback-assessment-routing.util.ts
103129
103081
  function diagnosticRoutingFallbackReason(artifact, routingRevision) {
103130
103082
  if (!artifact) {
@@ -103274,154 +103226,255 @@ var init_timeback_assessment_rules_util = __esm(() => {
103274
103226
  init_errors2();
103275
103227
  });
103276
103228
 
103277
- // ../api-core/src/utils/timeback-assessment-scoring.util.ts
103278
- async function scoreAssessmentItems({
103279
- assessment,
103280
- responses,
103281
- artifactIdentity,
103282
- purpose,
103283
- itemIdentifiers,
103284
- artifact,
103285
- processResponse,
103286
- onEvent
103287
- }) {
103288
- const scoringItems = assessmentScoringRequests(assessment, responses).filter((item) => !itemIdentifiers || itemIdentifiers.has(item.itemIdentifier));
103289
- const artifactItems = new Map(artifact?.items.map((item) => [item.itemIdentifier, item]));
103290
- const scored = new Map;
103291
- const qti2 = [];
103292
- for (const item of scoringItems) {
103293
- const artifactItem = artifactItems.get(item.itemIdentifier);
103294
- let fallback2 = null;
103295
- if (!artifact) {
103296
- fallback2 = { reason: "artifact-unavailable" };
103297
- } else if (!artifactItem) {
103298
- fallback2 = { reason: "artifact-item-missing" };
103299
- } else if (artifactItem.strategy === "qti") {
103300
- fallback2 = { reason: "artifact-qti", artifactReason: artifactItem.reason };
103301
- } else {
103302
- const startedAt = Date.now();
103303
- try {
103304
- scored.set(item.itemIdentifier, {
103305
- ...scorePlatformAssessmentItem(artifactItem, responses[item.itemIdentifier]),
103306
- grading: PLATFORM_CHILD_GRADING
103307
- });
103308
- onEvent("assessment.item_scoring", {
103309
- "app.assessment.qti_item_identifier": item.itemIdentifier,
103310
- "app.assessment.purpose": purpose,
103311
- "app.assessment.artifact_identity_kind": artifactIdentity.kind,
103312
- "app.assessment.grading_source": "platform-artifact",
103313
- "app.assessment.grading_duration_ms": Date.now() - startedAt
103314
- });
103315
- } catch (error88) {
103316
- fallback2 = { reason: "platform-grader-error", error: error88 };
103229
+ // ../api-core/src/utils/timeback-assessment-replay.util.ts
103230
+ function diagnosticReceipt(submission, ledgerEntry) {
103231
+ return {
103232
+ submissionId: submission.submissionId,
103233
+ routingNodeKey: ledgerEntry.routingNodeKey,
103234
+ itemIdentifier: submission.itemIdentifier,
103235
+ submittedAt: submission.submittedAt,
103236
+ responseVersion: submission.responseVersion,
103237
+ answered: submission.answered,
103238
+ score: submission.score,
103239
+ isCorrect: ledgerEntry.isCorrect
103240
+ };
103241
+ }
103242
+
103243
+ // ../api-core/src/utils/timeback-assessment-transcript.util.ts
103244
+ function parseAssessmentTranscript(value) {
103245
+ const parsed = AssessmentTranscriptSchema.safeParse(value);
103246
+ if (!parsed.success) {
103247
+ throw new ValidationError("Invalid assessment transcript");
103248
+ }
103249
+ return parsed.data;
103250
+ }
103251
+ function assessmentTokenMetadata(metadata2) {
103252
+ const context2 = { ...metadata2, updatedAt: metadata2.startedAt };
103253
+ delete context2.transcript;
103254
+ return context2;
103255
+ }
103256
+ function requireAssessmentTokenContext(context2, scope) {
103257
+ if (!context2 || context2.gameId !== scope.gameId || context2.studentId !== scope.studentId || context2.attemptId !== scope.attemptId) {
103258
+ throw AssessmentRuntimeError.from(assessmentAttemptUnauthorized(scope.attemptId));
103259
+ }
103260
+ return context2;
103261
+ }
103262
+ function requireAssessmentFeedbackContext(tokenContext, scope) {
103263
+ const context2 = requireAssessmentTokenContext(tokenContext, scope);
103264
+ const metadata2 = context2.metadata;
103265
+ if (assessmentFlowForPurpose(metadata2.purpose) === "attempt-submit" && !(metadata2.purpose === "diagnostic" && metadata2.diagnostic)) {
103266
+ throw AssessmentRuntimeError.from(assessmentFlowViolation("This assessment accepts a complete attempt, not per-item feedback."));
103267
+ }
103268
+ return context2;
103269
+ }
103270
+ function prepareAssessmentProgress(tokenContext, scope, input) {
103271
+ const context2 = requireAssessmentTokenContext(tokenContext, scope);
103272
+ const transcript = parseAssessmentTranscript(input.transcript);
103273
+ const progress = assessmentProgressAdministration(context2.metadata.purpose, {
103274
+ ...input,
103275
+ transcript
103276
+ });
103277
+ if (!progress.ok) {
103278
+ throw AssessmentRuntimeError.from(progress.failure);
103279
+ }
103280
+ return { context: context2, transcript, submissionId: progress.submissionId };
103281
+ }
103282
+ function prepareAssessmentItemTranscript(assessment, input) {
103283
+ validateAssessmentResponseUpdate(assessment, { [input.itemIdentifier]: input.responses });
103284
+ const appended = appendAssessmentAdministration(input.transcript, input);
103285
+ if (!appended.ok) {
103286
+ throw AssessmentRuntimeError.from(appended.failure);
103287
+ }
103288
+ return appended;
103289
+ }
103290
+ function prepareAttemptAssessmentTranscript(context2, assessment, transcript, terminal = false) {
103291
+ return prepareAssessmentTranscript({
103292
+ transcript,
103293
+ attemptId: context2.attemptId,
103294
+ contentRevision: context2.metadata.selectedTest.contentRevision,
103295
+ assessment,
103296
+ routed: context2.metadata.purpose === "diagnostic" && context2.metadata.diagnostic !== undefined,
103297
+ allowIncomplete: !terminal && assessmentFlowForPurpose(context2.metadata.purpose) === "attempt-submit"
103298
+ });
103299
+ }
103300
+ function requireTranscriptWriteAccess(attemptId, integrationStatus) {
103301
+ if (integrationStatus === "deactivated") {
103302
+ throw AssessmentRuntimeError.from(assessmentAttemptUnauthorized(attemptId));
103303
+ }
103304
+ }
103305
+ function requireTranscriptVersion(transcript, expectedResponseVersion) {
103306
+ if (expectedResponseVersion !== transcript.administrations.length) {
103307
+ throw AssessmentRuntimeError.from(assessmentResponseVersionConflict(expectedResponseVersion, transcript.administrations.length));
103308
+ }
103309
+ }
103310
+ function requireAssessmentTranscriptCheckpoint(attempt, integrationStatus, input) {
103311
+ requireTranscriptWriteAccess(attempt.sourcedId, integrationStatus);
103312
+ requireTranscriptVersion(input.transcript, input.expectedResponseVersion);
103313
+ if (!isAssessmentAttemptOpen({ ...attempt, inProgress: attempt.inProgress ?? "" })) {
103314
+ throw AssessmentRuntimeError.from(assessmentAttemptNotOpen(attempt.sourcedId));
103315
+ }
103316
+ }
103317
+ function assessmentTranscriptRequestHash(input) {
103318
+ return sha256Hex(canonicalJson2({
103319
+ transcript: input.transcript,
103320
+ ...input.session ? { session: input.session } : {}
103321
+ }));
103322
+ }
103323
+ function resolveAssessmentTranscriptSubmission(attempt, metadata2, integrationStatus, input, requestHash) {
103324
+ const disposition = classifyAssessmentSubmission({
103325
+ inProgress: attempt.inProgress ?? "",
103326
+ scoreStatus: attempt.scoreStatus,
103327
+ submissionId: metadata2.submissionId
103328
+ }, input.submissionId);
103329
+ if (disposition === "replay" && metadata2.completion?.requestHash === requestHash) {
103330
+ return "replay";
103331
+ }
103332
+ if (disposition === "reject") {
103333
+ throw AssessmentRuntimeError.from(assessmentAlreadySubmitted(attempt.sourcedId));
103334
+ }
103335
+ if (disposition !== "submit") {
103336
+ throw AssessmentRuntimeError.from(assessmentFlowViolation("A different completion was already accepted for this attempt."));
103337
+ }
103338
+ requireTranscriptWriteAccess(attempt.sourcedId, integrationStatus);
103339
+ if (input.session && input.session.runId !== attempt.sourcedId) {
103340
+ throw new ValidationError("Assessment session runId must match the attemptId.");
103341
+ }
103342
+ requireTranscriptVersion(input.transcript, input.expectedResponseVersion);
103343
+ return "submit";
103344
+ }
103345
+ function buildGradedAssessmentTranscript(input) {
103346
+ const { context: context2, prepared, itemResults, assessment, submittedAt, manifest } = input;
103347
+ const completed = completeAssessmentTranscript({
103348
+ transcript: prepared.transcript,
103349
+ scores: itemResults,
103350
+ purpose: context2.metadata.purpose,
103351
+ itemCount: assessment.items.length,
103352
+ submittedAt,
103353
+ manifest,
103354
+ terminal: input.terminal
103355
+ });
103356
+ const common2 = {
103357
+ ...context2.metadata,
103358
+ responses: prepared.responses,
103359
+ responseVersion: completed.itemSubmissions.length,
103360
+ itemSubmissions: completed.itemSubmissions,
103361
+ updatedAt: submittedAt,
103362
+ transcript: prepared.transcript
103363
+ };
103364
+ const diagnostic = context2.metadata.purpose === "diagnostic" ? context2.metadata.diagnostic : undefined;
103365
+ const metadata2 = diagnostic ? {
103366
+ ...common2,
103367
+ purpose: "diagnostic",
103368
+ diagnostic: { ...diagnostic, ledger: completed.ledger, state: completed.state }
103369
+ } : common2;
103370
+ return {
103371
+ metadata: metadata2,
103372
+ transcript: prepared.transcript,
103373
+ assessment,
103374
+ itemResults,
103375
+ manifest,
103376
+ score: aggregateAssessmentScore(itemResults.map((item) => item.score.earned), itemResults.reduce((sum, item) => sum + item.score.possible, 0)),
103377
+ correctQuestions: countCorrectQuestions(itemResults.map((item) => item.isCorrect))
103378
+ };
103379
+ }
103380
+ function assessmentTranscriptRuntimeError(error88) {
103381
+ return error88 instanceof AssessmentTranscriptError ? AssessmentRuntimeError.from({
103382
+ code: error88.code,
103383
+ message: error88.message,
103384
+ details: { transcriptIndex: error88.index }
103385
+ }) : error88;
103386
+ }
103387
+ function buildAssessmentTranscriptFeedback(attemptId, graded, index2) {
103388
+ const { metadata: metadata2 } = graded;
103389
+ const submission = metadata2.itemSubmissions[index2];
103390
+ const common2 = {
103391
+ attemptId,
103392
+ responseVersion: metadata2.responseVersion,
103393
+ status: "in_progress",
103394
+ transcript: graded.transcript
103395
+ };
103396
+ return metadata2.purpose === "diagnostic" && metadata2.diagnostic ? {
103397
+ ...common2,
103398
+ routing: projectDiagnosticRoutingSnapshot(metadata2.diagnostic.routingRevision, metadata2.diagnostic.state),
103399
+ submission: diagnosticReceipt(submission, metadata2.diagnostic.ledger[index2])
103400
+ } : {
103401
+ ...common2,
103402
+ responses: metadata2.responses,
103403
+ itemSubmissions: metadata2.itemSubmissions,
103404
+ submission
103405
+ };
103406
+ }
103407
+ async function resumeAssessmentTranscript(input) {
103408
+ const retained = input.suppliedTranscript ?? input.checkpointTranscript;
103409
+ let transcript = retained ?? await input.recover();
103410
+ while (true) {
103411
+ try {
103412
+ return await input.grade(transcript);
103413
+ } catch (error88) {
103414
+ const index2 = error88 instanceof AssessmentRuntimeError && isRecord(error88.details) ? error88.details.transcriptIndex : undefined;
103415
+ if (retained !== undefined || typeof index2 !== "number" || index2 < 0 || index2 >= transcript.administrations.length) {
103416
+ throw error88;
103317
103417
  }
103318
- }
103319
- if (fallback2) {
103320
- qti2.push({ item, ...fallback2 });
103418
+ transcript = {
103419
+ ...transcript,
103420
+ administrations: transcript.administrations.slice(0, index2)
103421
+ };
103321
103422
  }
103322
103423
  }
103323
- if (qti2.length > 0) {
103324
- const fallbackTasks = qti2.flatMap(({ item, reason, artifactReason, error: graderError }) => {
103325
- const interactionResults = [];
103326
- let startedAt;
103327
- let completedInteractions = 0;
103328
- let failed = false;
103329
- const omittedResponseCount = item.requests.filter((request) => responses[item.itemIdentifier]?.[request.identifier] === undefined).length;
103330
- let qtiRemoteInteractionCount = 0;
103331
- let syntheticIncorrectInteractionCount = 0;
103332
- function fallbackEvent(outcome) {
103333
- onEvent("assessment.item_scoring_fallback", {
103334
- "app.assessment.qti_item_identifier": item.itemIdentifier,
103335
- "app.assessment.purpose": purpose,
103336
- "app.assessment.artifact_identity_kind": artifactIdentity.kind,
103337
- "app.assessment.grading_source": omittedResponseCount > 0 ? "platform-qti-adapter" : "timeback-qti",
103338
- "app.assessment.fallback_reason": reason,
103339
- "app.assessment.fallback_duration_ms": startedAt === undefined ? 0 : Date.now() - startedAt,
103340
- "app.assessment.qti_remote_interaction_count": qtiRemoteInteractionCount,
103341
- "app.assessment.synthetic_incorrect_interaction_count": syntheticIncorrectInteractionCount,
103342
- ...artifactReason ? { "app.assessment.artifact_fallback_reason": artifactReason } : {},
103343
- ...graderError ? {
103344
- "app.assessment.platform_grader_exception_type": errorType(graderError)
103345
- } : {},
103346
- ...outcome
103347
- });
103348
- }
103349
- function completeItem() {
103350
- const earned = assessmentItemEarnedScore(interactionResults.map((result) => Number.isFinite(result.score) ? result.score : 0), item.maxScore);
103351
- const possible = item.maxScore;
103352
- fallbackEvent({ "app.assessment.fallback_outcome": "succeeded" });
103353
- scored.set(item.itemIdentifier, {
103354
- itemIdentifier: item.itemIdentifier,
103355
- score: {
103356
- earned,
103357
- possible,
103358
- normalized: possible === 0 ? 0 : earned / possible
103359
- },
103360
- isCorrect: assessmentItemCorrectness(interactionResults.map((result) => result.isCorrect)),
103361
- grading: omittedResponseCount > 0 ? PLATFORM_QTI_ADAPTER_CHILD_GRADING : QTI_CHILD_GRADING
103362
- });
103363
- }
103364
- if (item.requests.length === 0) {
103365
- completeItem();
103366
- return [];
103424
+ }
103425
+ function assessmentReviewTranscriptOutcomes(input) {
103426
+ const resultByItem = new Map(input.itemResults.map((item) => [item.itemIdentifier, item]));
103427
+ const administered = administeredItemIdentifiers(input.metadata.itemSubmissions);
103428
+ return input.metadata.review.selections.filter((selection) => administered.has(selection.itemIdentifier)).map((selection) => {
103429
+ const result = resultByItem.get(selection.itemIdentifier);
103430
+ const submission = itemAdministration(input.metadata.itemSubmissions, selection.itemIdentifier);
103431
+ if (!result || !submission) {
103432
+ throw new Error(`Scoring omitted selected review item ${selection.itemIdentifier}`);
103433
+ }
103434
+ return {
103435
+ itemIdentifier: selection.itemIdentifier,
103436
+ standard: selection.standard,
103437
+ answered: submission.answered,
103438
+ score: result.score,
103439
+ isCorrect: result.isCorrect
103440
+ };
103441
+ });
103442
+ }
103443
+ function assessmentReviewTranscriptExposures(input) {
103444
+ const exposures = [];
103445
+ const authoritative = new Set;
103446
+ for (const { result, metadata: metadata2 } of input.attempts) {
103447
+ const state = { ...result, inProgress: result.inProgress ?? "" };
103448
+ const settled = isAssessmentAttemptAwaitingAward(state) || isAssessmentAttemptCompleted(state);
103449
+ if (metadata2.purpose === "review" && (metadata2.transcript !== undefined || settled)) {
103450
+ authoritative.add(result.sourcedId);
103451
+ if (metadata2.review.bankRevision === input.bankRevision) {
103452
+ const accepted = settled ? metadata2.itemSubmissions.map((submission) => ({
103453
+ itemIdentifier: submission.itemIdentifier,
103454
+ deliveredAt: submission.submittedAt
103455
+ })) : metadata2.transcript.administrations.map((entry) => ({
103456
+ itemIdentifier: entry.itemIdentifier,
103457
+ deliveredAt: metadata2.updatedAt
103458
+ }));
103459
+ exposures.push(...accepted);
103367
103460
  }
103368
- return item.requests.map((request, index2) => async () => {
103369
- startedAt ??= Date.now();
103370
- try {
103371
- const response = responses[item.itemIdentifier]?.[request.identifier];
103372
- if (response === undefined) {
103373
- syntheticIncorrectInteractionCount += 1;
103374
- interactionResults[index2] = { score: 0, isCorrect: false };
103375
- } else {
103376
- qtiRemoteInteractionCount += 1;
103377
- interactionResults[index2] = await processResponse(item.itemIdentifier, request);
103378
- }
103379
- completedInteractions += 1;
103380
- if (completedInteractions === item.requests.length && !failed) {
103381
- completeItem();
103382
- }
103383
- } catch (error88) {
103384
- if (!failed) {
103385
- failed = true;
103386
- fallbackEvent({
103387
- "app.assessment.fallback_outcome": "failed",
103388
- "exception.type": errorType(error88),
103389
- "app.error.message": errorMessage(error88)
103390
- });
103391
- }
103392
- throw error88;
103393
- }
103394
- });
103395
- });
103396
- await runWithConcurrency(fallbackTasks, SCORING_CONCURRENCY, (task) => task());
103461
+ }
103397
103462
  }
103398
- return scoringItems.map((item) => {
103399
- const result = scored.get(item.itemIdentifier);
103400
- if (!result) {
103401
- throw new Error(`Assessment item ${item.itemIdentifier} was not scored`);
103463
+ for (const command of input.children) {
103464
+ const { attempt, administration, context: context2 } = command.payload;
103465
+ if (!authoritative.has(attempt.attemptId) && context2.kind === "review" && context2.bankRevision === input.bankRevision) {
103466
+ exposures.push({
103467
+ itemIdentifier: administration.itemIdentifier,
103468
+ deliveredAt: command.recordedAt
103469
+ });
103402
103470
  }
103403
- return result;
103404
- });
103471
+ }
103472
+ return exposures;
103405
103473
  }
103406
- var QTI_CHILD_GRADING, PLATFORM_QTI_ADAPTER_CHILD_GRADING, PLATFORM_CHILD_GRADING, SCORING_CONCURRENCY = 4;
103407
- var init_timeback_assessment_scoring_util = __esm(() => {
103474
+ var init_timeback_assessment_transcript_util = __esm(() => {
103408
103475
  init_assessment_runtime2();
103409
- init_assessment_artifact_envelope_util();
103476
+ init_errors2();
103410
103477
  init_timeback_assessment_runtime_util();
103411
- QTI_CHILD_GRADING = {
103412
- source: "timeback-qti",
103413
- graderVersion: "timeback-qti-process-response-v1"
103414
- };
103415
- PLATFORM_QTI_ADAPTER_CHILD_GRADING = {
103416
- source: "platform-qti-adapter",
103417
- graderVersion: "platform-qti-adapter-v1",
103418
- qtiGraderVersion: "timeback-qti-process-response-v1"
103419
- };
103420
- PLATFORM_CHILD_GRADING = {
103421
- source: "platform-artifact",
103422
- artifactVersion: `assessment-artifact-v${ASSESSMENT_ARTIFACT_SCHEMA_VERSION}`,
103423
- graderVersion: `platform-grader-v${ASSESSMENT_GRADER_PROTOCOL_VERSION}`
103424
- };
103425
103478
  });
103426
103479
 
103427
103480
  // ../api-core/src/utils/timeback-qti-hydration.util.ts
@@ -103508,20 +103561,19 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
103508
103561
  init_assessment_artifact_runtime_util();
103509
103562
  init_assessment_runtime_lock_util();
103510
103563
  init_timeback_assessment_artifacts_util();
103564
+ init_timeback_assessment_child_result_util();
103511
103565
  init_timeback_assessment_preparation_util();
103512
103566
  init_timeback_assessment_publication_util();
103513
- init_timeback_assessment_reconciliation_util();
103514
- init_timeback_assessment_replay_util();
103515
103567
  init_timeback_assessment_routing_util();
103516
103568
  init_timeback_assessment_rules_util();
103517
103569
  init_timeback_assessment_runtime_util();
103518
103570
  init_timeback_assessment_scoring_util();
103571
+ init_timeback_assessment_transcript_util();
103519
103572
  init_timeback_qti_hydration_util();
103520
103573
  await __promiseAll([
103521
103574
  init_dist5(),
103522
103575
  init_errors8(),
103523
- init_assessment_artifact_loader_util(),
103524
- init_timeback_assessment_child_result_util()
103576
+ init_assessment_artifact_loader_util()
103525
103577
  ]);
103526
103578
  AssessmentPreparationPrerequisiteError = class AssessmentPreparationPrerequisiteError extends Error {
103527
103579
  original;
@@ -103536,7 +103588,6 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
103536
103588
  static PREPARATION_IN_FLIGHT_LIMIT = 32;
103537
103589
  static ASSESSMENT_CACHE_TTL_MS = 60000;
103538
103590
  static EXPORT_CONCURRENCY = 4;
103539
- static ITEM_SUBMISSION_MAX_PREPARATION_ATTEMPTS = 3;
103540
103591
  static ASSESSMENT_VERSION_CONFIRMATION_DELAY_MS = 25;
103541
103592
  static SCORING_CONCURRENCY = 4;
103542
103593
  static STALE_AWARD_AGE_MS = 15 * 60 * 1000;
@@ -103554,6 +103605,14 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
103554
103605
  maxSize: TimebackAssessmentRuntimeService.ASSESSMENT_CACHE_LIMIT
103555
103606
  });
103556
103607
  artifactLoader;
103608
+ itemScoreCache = new TimebackCache({
103609
+ defaultTTL: 5 * 60000,
103610
+ maxSize: 512
103611
+ });
103612
+ childLineItemCache = new TimebackCache({
103613
+ defaultTTL: 5 * 60000,
103614
+ maxSize: 512
103615
+ });
103557
103616
  preparations = new AssessmentPreparationFlights(TimebackAssessmentRuntimeService.PREPARATION_IN_FLIGHT_LIMIT);
103558
103617
  constructor(deps) {
103559
103618
  this.deps = deps;
@@ -103680,6 +103739,7 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
103680
103739
  version: PLAYCADEMY_ASSESSMENT_RESULT_METADATA_VERSION,
103681
103740
  activityId: input.activityId,
103682
103741
  courseId: context2.integration.courseId,
103742
+ courseName: context2.enrollment.course.title,
103683
103743
  integrationId: context2.integration.id,
103684
103744
  enrollmentId: context2.enrollment.id,
103685
103745
  selectedTest: {
@@ -103736,7 +103796,7 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
103736
103796
  enrollmentId: context2.enrollment.id
103737
103797
  };
103738
103798
  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);
103739
- const listing = this.attemptListing(results, attemptScope);
103799
+ const listing = await this.attemptListing(results, attemptScope);
103740
103800
  const decision = selectRuntimeAssessment(tests, [
103741
103801
  ...this.attemptsMatchingRequest(listing.attempts, this.startRequestFingerprint(params.input)).values()
103742
103802
  ].map((entry) => entry.selection));
@@ -103759,6 +103819,18 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
103759
103819
  let prepared;
103760
103820
  try {
103761
103821
  prepared = await this.buildPreparationPlan(params, requestHash, preparation);
103822
+ const assessment = await this.loadPreparedPlanAssessment(prepared.plan);
103823
+ if (!assessment) {
103824
+ throw new Error("Prepared assessment artifact is unavailable");
103825
+ }
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));
103762
103834
  } catch (error88) {
103763
103835
  if (error88 instanceof ValidationError || error88 instanceof AssessmentRuntimeError) {
103764
103836
  throw error88;
@@ -103844,6 +103916,7 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
103844
103916
  integrationId: context2.integration.id,
103845
103917
  enrollmentId: context2.enrollment.id,
103846
103918
  courseId: context2.integration.courseId,
103919
+ courseName: context2.enrollment.course.title,
103847
103920
  ...attempt,
103848
103921
  parentLineItemId,
103849
103922
  selectedTest: {
@@ -103884,7 +103957,9 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
103884
103957
  const selected = selectReviewItems({
103885
103958
  request: params.input,
103886
103959
  bank: catalog.bank,
103887
- exposures: this.reviewExposures(listing.reviewChildren.values(), {
103960
+ exposures: assessmentReviewTranscriptExposures({
103961
+ attempts: listing.attempts.values(),
103962
+ children: listing.reviewChildren.values(),
103888
103963
  bankRevision: catalog.bank.bankRevision
103889
103964
  })
103890
103965
  });
@@ -103933,6 +104008,7 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
103933
104008
  integrationId: context2.integration.id,
103934
104009
  enrollmentId: context2.enrollment.id,
103935
104010
  courseId: context2.integration.courseId,
104011
+ courseName: context2.enrollment.course.title,
103936
104012
  ...attempt,
103937
104013
  parentLineItemId,
103938
104014
  selectedTest: {
@@ -104084,6 +104160,7 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
104084
104160
  activityId: input.activityId,
104085
104161
  purpose: "diagnostic",
104086
104162
  courseId: context2.integration.courseId,
104163
+ courseName: context2.enrollment.course.title,
104087
104164
  integrationId: context2.integration.id,
104088
104165
  enrollmentId: context2.enrollment.id,
104089
104166
  selectedTest: {
@@ -104103,7 +104180,6 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
104103
104180
  testName: assessment.title,
104104
104181
  routingRevision: routing.routingRevision,
104105
104182
  ledger: [],
104106
- routingSnapshots: [],
104107
104183
  state: routing.state
104108
104184
  }
104109
104185
  };
@@ -104122,7 +104198,9 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
104122
104198
  })
104123
104199
  });
104124
104200
  await this.persistPendingSupersessionsBestEffort(db2, pendingSupersessions);
104125
- return this.diagnosticSnapshot(result, metadata2, assessment, context2.integration, routing.state);
104201
+ return this.diagnosticSnapshot(result, metadata2, assessment, context2.integration, {
104202
+ state: routing.state
104203
+ });
104126
104204
  });
104127
104205
  }
104128
104206
  async startReview({
@@ -104156,7 +104234,7 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
104156
104234
  this.listAttempts(attemptScope)
104157
104235
  ]);
104158
104236
  const attempts = listing.attempts;
104159
- const initialResolution = await this.resumeAvailableAttempt(tests, attempts, requestFingerprint, pendingSupersessions, (resumeDecision) => this.resumeReviewSnapshot(resumeDecision, attempts, listing.reviewChildren, context2));
104237
+ const initialResolution = await this.resumeAvailableAttempt(tests, attempts, requestFingerprint, pendingSupersessions, (resumeDecision) => this.resumeReviewSnapshot(resumeDecision, attempts, context2));
104160
104238
  if (initialResolution.kind === "resumed") {
104161
104239
  await this.persistPendingSupersessionsBestEffort(db2, pendingSupersessions);
104162
104240
  return initialResolution.snapshot;
@@ -104175,7 +104253,7 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
104175
104253
  });
104176
104254
  const refreshedListing = await this.listAttempts(attemptScope);
104177
104255
  const refreshed = refreshedListing.attempts;
104178
- const refreshedResolution = await this.resumeAvailableAttempt(tests, refreshed, requestFingerprint, pendingSupersessions, (resumeDecision) => this.resumeReviewSnapshot(resumeDecision, refreshed, refreshedListing.reviewChildren, context2));
104256
+ const refreshedResolution = await this.resumeAvailableAttempt(tests, refreshed, requestFingerprint, pendingSupersessions, (resumeDecision) => this.resumeReviewSnapshot(resumeDecision, refreshed, context2));
104179
104257
  if (refreshedResolution.kind === "resumed") {
104180
104258
  await this.persistPendingSupersessionsBestEffort(db2, pendingSupersessions);
104181
104259
  return refreshedResolution.snapshot;
@@ -104188,7 +104266,9 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
104188
104266
  if (catalog.test.identifier !== decision.test.qtiTestIdentifier) {
104189
104267
  catalog = await this.loadReviewBankCatalog(decision.test.qtiTestIdentifier);
104190
104268
  }
104191
- const exposures = this.reviewExposures(refreshedListing.reviewChildren.values(), {
104269
+ const exposures = assessmentReviewTranscriptExposures({
104270
+ attempts: refreshedListing.attempts.values(),
104271
+ children: refreshedListing.reviewChildren.values(),
104192
104272
  bankRevision: catalog.bank.bankRevision
104193
104273
  });
104194
104274
  const selected = selectReviewItems({
@@ -104227,6 +104307,7 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
104227
104307
  activityId: input.activityId,
104228
104308
  purpose: input.purpose,
104229
104309
  courseId: context2.integration.courseId,
104310
+ courseName: context2.enrollment.course.title,
104230
104311
  integrationId: context2.integration.id,
104231
104312
  enrollmentId: context2.enrollment.id,
104232
104313
  selectedTest: {
@@ -104273,8 +104354,14 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
104273
104354
  });
104274
104355
  }
104275
104356
  async get(params) {
104357
+ return this.resume({ ...params, input: {} });
104358
+ }
104359
+ async resume({
104360
+ input,
104361
+ ...params
104362
+ }) {
104276
104363
  const attempt = await this.authorizeAttemptRead(params);
104277
- return this.snapshotForResult(attempt.result, attempt.metadata, attempt.integration);
104364
+ return this.snapshotForResult(attempt.result, attempt.metadata, attempt.integration, input.transcript);
104278
104365
  }
104279
104366
  async latest({
104280
104367
  gameId,
@@ -104336,496 +104423,104 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
104336
104423
  }
104337
104424
  async save({ input, ...params }) {
104338
104425
  await this.deps.validateDeveloperAccess(params.user, params.gameId);
104339
- try {
104340
- const warmup = await this.peekAttempt(params);
104341
- if (this.isOpen(warmup.result) && !this.isPlatformRoutedDiagnosticMetadata(warmup.metadata)) {
104342
- await this.loadAttemptAssessment(warmup.metadata);
104343
- }
104344
- } catch (error88) {
104345
- addEvent("assessment.save_warmup_failed", {
104346
- "app.assessment.attempt_id": params.attemptId,
104347
- "exception.type": errorType(error88),
104348
- "app.error.message": errorMessage(error88)
104349
- });
104350
- }
104351
104426
  return this.deps.assessmentRuntimeLock(this.deps.db, [assessmentRuntimeAttemptLockKey(params.attemptId)], async (db2) => {
104352
- const attempt = await this.authorizeAttempt(params, {
104427
+ const { attempt } = await this.resolveAttemptAuthorization(params, {
104353
104428
  db: db2,
104354
- developerAccessValidated: true
104429
+ developerAccessValidated: true,
104430
+ includeEnrollment: false
104355
104431
  });
104356
- if (this.isPlatformRoutedDiagnosticMetadata(attempt.metadata)) {
104357
- throw AssessmentRuntimeError.from(assessmentFlowViolation("Platform-routed diagnostics do not accept draft saves.", { flow: "platform-routed-item-submit" }));
104358
- }
104359
- this.assertInProgress(attempt.result);
104360
- this.assertResponseVersion(attempt.metadata, input.expectedResponseVersion);
104361
- const assessment = await this.loadAttemptAssessment(attempt.metadata);
104362
- const flowFailure = assessmentResponseUpdateFlowFailure(attempt.metadata.purpose, attempt.metadata.itemSubmissions, input.responses);
104363
- if (flowFailure) {
104364
- throw AssessmentRuntimeError.from(flowFailure);
104365
- }
104366
- validateAssessmentResponseUpdate(assessment, input.responses);
104367
- const responses = applyAssessmentResponseUpdate(attempt.metadata.responses, input.responses);
104368
- validateAssessmentDraftResponses(assessment, responses);
104369
- const metadata2 = {
104370
- ...attempt.metadata,
104371
- responses,
104372
- responseVersion: attempt.metadata.responseVersion + 1,
104373
- updatedAt: new Date().toISOString()
104374
- };
104375
- await this.putResultMetadata(attempt.result, metadata2);
104432
+ requireAssessmentTranscriptCheckpoint(attempt.result, attempt.integration.status, input);
104433
+ const graded = await this.gradeTranscript({
104434
+ attemptId: params.attemptId,
104435
+ metadata: attempt.metadata
104436
+ }, input.transcript, false, db2);
104437
+ await this.requireClient().api.oneroster.assessmentResults.upsert(params.attemptId, buildAssessmentResultCheckpoint({
104438
+ result: attempt.result,
104439
+ metadata: attempt.metadata,
104440
+ transcript: graded.transcript,
104441
+ timestamp: graded.metadata.updatedAt
104442
+ }));
104443
+ this.reconcileTranscriptChildren(this.tokenContext(attempt), graded).catch((error88) => this.recordTranscriptFailure("checkpoint_projection", params.attemptId, error88));
104376
104444
  return {
104377
- attemptId: attempt.result.sourcedId,
104378
- responseVersion: metadata2.responseVersion,
104445
+ attemptId: params.attemptId,
104446
+ responseVersion: graded.metadata.responseVersion,
104379
104447
  status: "in_progress",
104380
- responses
104448
+ responses: graded.metadata.responses,
104449
+ transcript: graded.transcript
104381
104450
  };
104382
104451
  });
104383
104452
  }
104384
- async submitItem({
104453
+ async recordProgress({
104385
104454
  input,
104386
104455
  ...params
104387
104456
  }) {
104388
104457
  await this.deps.validateDeveloperAccess(params.user, params.gameId);
104389
- if (input.routingNodeKey !== undefined) {
104390
- const identity = await this.peekAttempt(params);
104391
- if (this.isPlatformRoutedDiagnosticMetadata(identity.metadata)) {
104392
- return this.submitDiagnosticItem(params, input);
104393
- }
104394
- throw AssessmentRuntimeError.from(assessmentFlowViolation("routingNodeKey is not accepted by the hosted conventional item-submit flow.", { routingNodeKey: input.routingNodeKey }));
104395
- }
104396
- let preview = await this.prepareItemSubmission(params, input);
104397
- let preparationAttempts = 1;
104398
- while (true) {
104399
- const committed = await this.deps.assessmentRuntimeLock(this.deps.db, [assessmentRuntimeAttemptLockKey(params.attemptId)], async (db2) => {
104400
- const { attempt, enrollment } = await this.resolveAttemptAuthorization(params, {
104401
- db: db2,
104402
- developerAccessValidated: true
104403
- });
104404
- if (this.isPlatformRoutedDiagnosticMetadata(attempt.metadata)) {
104405
- this.requireActiveAttempt({ attempt, enrollment }, params.attemptId);
104406
- throw AssessmentRuntimeError.from(assessmentFlowViolation("Platform-routed diagnostic submissions require routingNodeKey.", { flow: "platform-routed-item-submit" }));
104407
- }
104408
- const replay = resolveAssessmentItemReplay(attempt.metadata, input);
104409
- if (replay.action === "conflict") {
104410
- this.requireActiveAttempt({ attempt, enrollment }, params.attemptId);
104411
- throw AssessmentRuntimeError.from(replay.failure);
104412
- }
104413
- if (replay.action === "replay") {
104414
- const prior = replay.submission;
104415
- const child2 = attempt.metadata.purpose === "review" ? await this.prepareReviewAssessmentChildResult({
104416
- attempt,
104417
- metadata: attempt.metadata,
104418
- submission: prior
104419
- }) : null;
104420
- return {
104421
- action: "complete",
104422
- response: buildAssessmentItemReplayResult(attempt.result.sourcedId, attempt.metadata, prior),
104423
- checkpoint: child2,
104424
- projection: attempt.metadata.purpose === "review" ? {
104425
- result: attempt.result,
104426
- metadata: attempt.metadata,
104427
- itemIdentifier: input.itemIdentifier,
104428
- integration: attempt.integration
104429
- } : null
104430
- };
104431
- }
104432
- this.requireActiveAttempt({ attempt, enrollment }, params.attemptId);
104433
- this.assertInProgress(attempt.result);
104434
- if (!preview || preview.responseVersion !== attempt.metadata.responseVersion || preview.submissionId !== input.submissionId || preview.itemIdentifier !== input.itemIdentifier) {
104435
- if (preparationAttempts >= TimebackAssessmentRuntimeService.ITEM_SUBMISSION_MAX_PREPARATION_ATTEMPTS) {
104436
- throw assessmentPreparationContentionError(input.expectedResponseVersion, attempt.metadata.responseVersion, "assessment");
104437
- }
104438
- return { action: "prepare" };
104439
- }
104440
- const transition = prepareAssessmentItemSubmission({
104441
- purpose: attempt.metadata.purpose,
104442
- responseVersion: attempt.metadata.responseVersion,
104443
- responses: attempt.metadata.responses,
104444
- itemSubmissions: attempt.metadata.itemSubmissions,
104445
- assessment: preview.assessment
104446
- }, input);
104447
- if (transition.action === "reject") {
104448
- throw AssessmentRuntimeError.from(transition.failure);
104449
- }
104450
- if (transition.action === "replay") {
104451
- const child2 = attempt.metadata.purpose === "review" ? await this.prepareReviewAssessmentChildResult({
104452
- attempt,
104453
- metadata: attempt.metadata,
104454
- submission: transition.submission
104455
- }) : null;
104456
- return {
104457
- action: "complete",
104458
- response: {
104459
- attemptId: attempt.result.sourcedId,
104460
- responseVersion: transition.responseVersion,
104461
- status: "in_progress",
104462
- responses: transition.responses,
104463
- itemSubmissions: attempt.metadata.itemSubmissions,
104464
- submission: transition.submission
104465
- },
104466
- checkpoint: child2,
104467
- projection: attempt.metadata.purpose === "review" ? {
104468
- result: attempt.result,
104469
- metadata: attempt.metadata,
104470
- itemIdentifier: input.itemIdentifier,
104471
- integration: attempt.integration
104472
- } : null
104473
- };
104474
- }
104475
- if (!preview.scoring) {
104476
- throw new Error("Prepared item submission is missing its score");
104477
- }
104478
- const submittedAt = new Date().toISOString();
104479
- const completed = completeAssessmentItemSubmission(attempt.metadata, input, transition, preview.scoring, submittedAt);
104480
- const metadata2 = {
104481
- ...attempt.metadata,
104482
- responses: completed.responses,
104483
- responseVersion: completed.responseVersion,
104484
- itemSubmissions: completed.itemSubmissions,
104485
- updatedAt: submittedAt
104486
- };
104487
- const child = metadata2.purpose === "review" ? await this.prepareReviewAssessmentChildResult({
104488
- attempt,
104489
- metadata: metadata2,
104490
- submission: completed.submission
104491
- }) : null;
104492
- await this.putResultMetadata(attempt.result, metadata2);
104493
- return {
104494
- action: "complete",
104495
- response: {
104496
- attemptId: attempt.result.sourcedId,
104497
- responseVersion: completed.responseVersion,
104498
- status: "in_progress",
104499
- responses: completed.responses,
104500
- itemSubmissions: completed.itemSubmissions,
104501
- submission: completed.submission
104502
- },
104503
- checkpoint: child,
104504
- projection: metadata2.purpose === "review" ? {
104505
- result: attempt.result,
104506
- metadata: metadata2,
104507
- itemIdentifier: input.itemIdentifier,
104508
- integration: attempt.integration
104509
- } : null
104510
- };
104511
- });
104512
- if (committed.action === "prepare") {
104513
- preparationAttempts += 1;
104514
- preview = await this.scoreItemSubmission(params, input);
104515
- } else {
104516
- if (committed.projection) {
104517
- this.projectReviewItemResponse(params, committed.projection, committed.checkpoint);
104518
- }
104519
- return committed.response;
104520
- }
104521
- }
104522
- }
104523
- async submitDiagnosticItem(params, input) {
104524
- let preview = await this.prepareDiagnosticItemSubmission(params, input);
104525
- let preparationAttempts = 1;
104526
- while (true) {
104527
- const committed = await this.deps.assessmentRuntimeLock(this.deps.db, [assessmentRuntimeAttemptLockKey(params.attemptId)], async (db2) => {
104528
- const { attempt, enrollment } = await this.resolveAttemptAuthorization(params, { db: db2, developerAccessValidated: true });
104529
- if (!this.isPlatformRoutedDiagnosticMetadata(attempt.metadata)) {
104530
- this.requireActiveAttempt({ attempt, enrollment }, params.attemptId);
104531
- throw AssessmentRuntimeError.from(assessmentFlowViolation("routingNodeKey is accepted only for a platform-routed diagnostic.", { routingNodeKey: input.routingNodeKey }));
104532
- }
104533
- const metadata2 = attempt.metadata;
104534
- const replay = resolveDiagnosticItemReplay(metadata2, input);
104535
- if (replay.action === "conflict") {
104536
- this.requireActiveAttempt({ attempt, enrollment }, params.attemptId);
104537
- throw AssessmentRuntimeError.from(replay.failure);
104538
- }
104539
- if (replay.action === "replay") {
104540
- const { submission: prior, ledgerEntry, priorIndex } = replay;
104541
- let routing = replay.routing;
104542
- if (!routing) {
104543
- const assessment = preview?.assessment ?? await this.loadAttemptAssessment(metadata2);
104544
- const loaded2 = await this.loadAttemptDiagnosticRouting(metadata2, assessment, db2);
104545
- const replayed = replayDiagnosticRouting(loaded2.manifest, metadata2.diagnostic.ledger.slice(0, priorIndex + 1));
104546
- if (!replayed.ok) {
104547
- throw AssessmentRuntimeError.from(assessmentFlowViolation("The committed diagnostic routing state cannot be replayed.", { failure: replayed.failure }));
104548
- }
104549
- routing = this.diagnosticRoutingSnapshot(metadata2, replayed.state);
104550
- }
104551
- const child2 = await this.prepareDiagnosticAssessmentChildResult({
104552
- attempt,
104553
- metadata: metadata2,
104554
- submission: prior,
104555
- transition: ledgerEntry
104556
- });
104557
- return {
104558
- action: "complete",
104559
- response: buildDiagnosticItemReplayResult(attempt.result.sourcedId, prior, ledgerEntry, routing),
104560
- checkpoint: child2,
104561
- projection: {
104562
- result: attempt.result,
104563
- metadata: metadata2,
104564
- ledgerEntry,
104565
- integration: attempt.integration
104566
- }
104567
- };
104568
- }
104569
- this.requireActiveAttempt({ attempt, enrollment }, params.attemptId);
104570
- this.assertInProgress(attempt.result);
104571
- if (!preview || preview.responseVersion !== metadata2.responseVersion || preview.submissionId !== input.submissionId || preview.routingNodeKey !== input.routingNodeKey || preview.itemIdentifier !== input.itemIdentifier) {
104572
- if (preparationAttempts >= TimebackAssessmentRuntimeService.ITEM_SUBMISSION_MAX_PREPARATION_ATTEMPTS) {
104573
- throw assessmentPreparationContentionError(input.expectedResponseVersion, metadata2.responseVersion, "diagnostic");
104574
- }
104575
- return { action: "prepare" };
104576
- }
104577
- this.assertResponseVersion(metadata2, input.expectedResponseVersion);
104578
- const loaded = await this.loadAttemptDiagnosticRouting(metadata2, preview.assessment, db2);
104579
- const expected = loaded.state.next;
104580
- if (loaded.state.status !== "in-progress" || !expected || expected.nodeKey !== input.routingNodeKey || expected.itemIdentifier !== input.itemIdentifier) {
104581
- throw AssessmentRuntimeError.from(assessmentFlowViolation(expected ? "Submitted diagnostic item is not the canonical next item." : "Diagnostic routing is already ready to complete.", {
104582
- expectedRoutingNodeKey: expected?.nodeKey ?? null,
104583
- routingNodeKey: input.routingNodeKey,
104584
- expectedItemIdentifier: expected?.itemIdentifier ?? null,
104585
- itemIdentifier: input.itemIdentifier
104586
- }));
104587
- }
104588
- const responses = prepareDiagnosticAssessmentResponses(preview.assessment, metadata2.responses, input);
104589
- const scoring = preview.scoring;
104590
- if (!scoring || typeof scoring.isCorrect !== "boolean") {
104591
- throw AssessmentRuntimeError.from(assessmentFlowViolation(`Diagnostic item ${input.itemIdentifier} did not produce determinate binary grading.`, { itemIdentifier: input.itemIdentifier }));
104592
- }
104593
- const advanced = advanceDiagnosticRouting(loaded.manifest, metadata2.diagnostic.ledger, {
104594
- routingNodeKey: input.routingNodeKey,
104595
- itemIdentifier: input.itemIdentifier,
104596
- isCorrect: scoring.isCorrect
104597
- });
104598
- if (!advanced.ok) {
104599
- throw AssessmentRuntimeError.from(assessmentFlowViolation("The diagnostic routing transition was rejected.", { failure: advanced.failure }));
104600
- }
104601
- const submittedAt = new Date().toISOString();
104602
- const responseVersion = metadata2.responseVersion + 1;
104603
- const routingSnapshot = this.diagnosticRoutingSnapshot(metadata2, advanced.state);
104604
- const routingSnapshots = metadata2.diagnostic.routingSnapshots ? [...metadata2.diagnostic.routingSnapshots, routingSnapshot] : undefined;
104605
- const submission = {
104606
- submissionId: input.submissionId,
104607
- itemIdentifier: input.itemIdentifier,
104608
- submittedAt,
104609
- responseVersion,
104610
- answered: Object.keys(responses[input.itemIdentifier] ?? {}).length > 0,
104611
- score: scoring.score,
104612
- isCorrect: scoring.isCorrect,
104613
- grading: scoring.grading
104614
- };
104615
- const nextMetadata = {
104616
- ...metadata2,
104617
- responses,
104618
- responseVersion,
104619
- itemSubmissions: [...metadata2.itemSubmissions, submission],
104620
- updatedAt: submittedAt,
104621
- diagnostic: {
104622
- ...metadata2.diagnostic,
104623
- ledger: [...metadata2.diagnostic.ledger, advanced.ledgerEntry],
104624
- ...routingSnapshots ? { routingSnapshots } : {},
104625
- state: advanced.state
104626
- }
104627
- };
104628
- const child = await this.prepareDiagnosticAssessmentChildResult({
104629
- attempt,
104630
- metadata: nextMetadata,
104631
- submission,
104632
- transition: advanced.ledgerEntry
104633
- });
104634
- await this.putResultMetadata(attempt.result, nextMetadata);
104635
- return {
104636
- action: "complete",
104637
- response: {
104638
- attemptId: attempt.result.sourcedId,
104639
- responseVersion,
104640
- status: "in_progress",
104641
- routing: routingSnapshot,
104642
- submission: diagnosticReceipt(submission, advanced.ledgerEntry)
104643
- },
104644
- checkpoint: child,
104645
- projection: {
104646
- result: attempt.result,
104647
- metadata: nextMetadata,
104648
- ledgerEntry: advanced.ledgerEntry,
104649
- integration: attempt.integration
104650
- }
104651
- };
104652
- });
104653
- if (committed.action === "prepare") {
104654
- preparationAttempts += 1;
104655
- preview = await this.scoreDiagnosticItemSubmission(params, input);
104656
- } else {
104657
- this.projectDiagnosticItemResponse(committed.projection, preview?.assessment, committed.checkpoint);
104658
- return committed.response;
104659
- }
104660
- }
104458
+ const { context: context2, transcript, submissionId } = prepareAssessmentProgress(await this.deps.verifyAssessmentToken(input.assessmentToken), params, input);
104459
+ const graded = await this.gradeTranscript(context2, transcript);
104460
+ await this.reconcileTranscriptChildren(context2, graded, new Set([submissionId]));
104461
+ return { attemptId: params.attemptId };
104661
104462
  }
104662
- async prepareDiagnosticItemSubmission(params, input) {
104463
+ async submitItem({
104464
+ input,
104465
+ ...params
104466
+ }) {
104467
+ await this.deps.validateDeveloperAccess(params.user, params.gameId);
104468
+ const context2 = requireAssessmentFeedbackContext(await this.deps.verifyAssessmentToken(input.assessmentToken), params);
104469
+ const transcript = parseAssessmentTranscript(input.transcript);
104470
+ const assessment = await this.loadAttemptAssessment(context2.metadata);
104471
+ const appended = prepareAssessmentItemTranscript(assessment, { ...input, transcript });
104472
+ const graded = await this.gradeTranscript(context2, appended.transcript);
104473
+ const submission = graded.metadata.itemSubmissions[appended.index];
104474
+ this.reconcileTranscriptChildren(context2, graded, new Set([submission.submissionId])).catch((error88) => this.recordTranscriptFailure("child_projection", params.attemptId, error88));
104475
+ return buildAssessmentTranscriptFeedback(params.attemptId, graded, appended.index);
104476
+ }
104477
+ async gradeTranscript(context2, transcript, terminal = false, db2 = this.deps.db) {
104478
+ const startedAt = Date.now();
104663
104479
  try {
104664
- return await this.scoreDiagnosticItemSubmission(params, input);
104665
- } catch (error88) {
104666
- addEvent("assessment.diagnostic_item_submission_preview_failed", {
104667
- "app.assessment.attempt_id": params.attemptId,
104668
- "exception.type": errorType(error88),
104669
- "app.error.message": errorMessage(error88)
104670
- });
104671
- return null;
104672
- }
104673
- }
104674
- async scoreDiagnosticItemSubmission(params, input) {
104675
- const attempt = await this.peekAttempt(params);
104676
- if (!this.isPlatformRoutedDiagnosticMetadata(attempt.metadata)) {
104677
- return null;
104678
- }
104679
- const prior = attempt.metadata.itemSubmissions.find((submission) => submission.submissionId === input.submissionId);
104680
- if (prior || !this.isOpen(attempt.result)) {
104681
- return null;
104682
- }
104683
- const assessment = await this.loadAttemptAssessment(attempt.metadata);
104684
- const artifactIdentity = assessmentArtifactIdentityFromAttempt(attempt.metadata);
104685
- const scoringArtifact = await this.artifactLoader.loadMatchedScoring(assessment, artifactIdentity);
104686
- const loaded = await this.loadAttemptDiagnosticRouting(attempt.metadata, assessment, this.deps.db, scoringArtifact);
104687
- const expected = loaded.state.next;
104688
- let scoring = null;
104689
- if (loaded.state.status === "in-progress" && expected?.nodeKey === input.routingNodeKey && expected.itemIdentifier === input.itemIdentifier && attempt.metadata.responseVersion === input.expectedResponseVersion) {
104690
- const responses = prepareDiagnosticAssessmentResponses(assessment, attempt.metadata.responses, input);
104691
- scoring = await this.scoreItem({
104480
+ const assessment = await this.loadAttemptAssessment(context2.metadata);
104481
+ const prepared = prepareAttemptAssessmentTranscript(context2, assessment, transcript, terminal);
104482
+ const artifactIdentity = assessmentArtifactIdentityFromAttempt(context2.metadata);
104483
+ const artifact = await this.artifactLoader.loadMatchedScoring(assessment, artifactIdentity);
104484
+ const [itemResults, routing] = await Promise.all([
104485
+ scoreCachedAssessmentItems({
104486
+ assessment,
104487
+ responses: prepared.responses,
104488
+ artifactIdentity,
104489
+ purpose: context2.metadata.purpose,
104490
+ itemIdentifiers: new Set(prepared.transcript.administrations.map((entry) => entry.itemIdentifier)),
104491
+ artifact,
104492
+ cache: this.itemScoreCache,
104493
+ processResponse: (identifier, request) => this.requireClient().qtiApi.assessmentItems.processResponse(identifier, request),
104494
+ onEvent: addEvent
104495
+ }),
104496
+ this.isPlatformRoutedDiagnosticMetadata(context2.metadata) ? this.loadAttemptDiagnosticRouting(context2.metadata, assessment, db2, artifact) : undefined
104497
+ ]);
104498
+ const graded = buildGradedAssessmentTranscript({
104499
+ context: context2,
104692
104500
  assessment,
104693
- responses,
104694
- itemIdentifier: input.itemIdentifier,
104695
- artifactIdentity,
104696
- purpose: attempt.metadata.purpose,
104697
- preparedArtifact: scoringArtifact
104501
+ prepared,
104502
+ itemResults,
104503
+ submittedAt: new Date().toISOString(),
104504
+ manifest: routing?.manifest,
104505
+ terminal
104506
+ });
104507
+ addEvent("assessment.transcript_validated", {
104508
+ "app.assessment.attempt_id": context2.attemptId,
104509
+ "app.assessment.administration_count": graded.metadata.responseVersion,
104510
+ "app.assessment.duration_ms": Date.now() - startedAt
104698
104511
  });
104699
- }
104700
- return {
104701
- responseVersion: attempt.metadata.responseVersion,
104702
- submissionId: input.submissionId,
104703
- routingNodeKey: input.routingNodeKey,
104704
- itemIdentifier: input.itemIdentifier,
104705
- assessment,
104706
- scoring
104707
- };
104708
- }
104709
- async scoreItems({
104710
- preparedArtifact,
104711
- ...input
104712
- }) {
104713
- const artifact = preparedArtifact === undefined ? await this.artifactLoader.loadMatchedScoring(input.assessment, input.artifactIdentity) : preparedArtifact;
104714
- return scoreAssessmentItems({
104715
- ...input,
104716
- artifact,
104717
- processResponse: (identifier, request) => this.requireClient().qtiApi.assessmentItems.processResponse(identifier, request),
104718
- onEvent: addEvent
104719
- });
104720
- }
104721
- async scoreItem(input) {
104722
- const [scoring] = await this.scoreItems({
104723
- assessment: input.assessment,
104724
- responses: input.responses,
104725
- artifactIdentity: input.artifactIdentity,
104726
- purpose: input.purpose,
104727
- itemIdentifiers: new Set([input.itemIdentifier]),
104728
- preparedArtifact: input.preparedArtifact
104729
- });
104730
- return scoring;
104731
- }
104732
- async prepareItemSubmission(params, input) {
104733
- try {
104734
- return await this.scoreItemSubmission(params, input);
104512
+ return graded;
104735
104513
  } catch (error88) {
104736
- addEvent("assessment.item_submission_preview_failed", {
104737
- "app.assessment.attempt_id": params.attemptId,
104738
- "exception.type": errorType(error88),
104739
- "app.error.message": errorMessage(error88)
104740
- });
104741
- return null;
104514
+ throw assessmentTranscriptRuntimeError(error88);
104742
104515
  }
104743
104516
  }
104744
- async scoreItemSubmission(params, input) {
104745
- const attempt = await this.peekAttempt(params);
104746
- const prior = attempt.metadata.itemSubmissions.find((submission) => submission.submissionId === input.submissionId);
104747
- if (prior || !this.isOpen(attempt.result)) {
104748
- return null;
104749
- }
104750
- const assessment = await this.loadAttemptAssessment(attempt.metadata);
104751
- const transition = prepareAssessmentItemSubmission({
104752
- purpose: attempt.metadata.purpose,
104753
- responseVersion: attempt.metadata.responseVersion,
104754
- responses: attempt.metadata.responses,
104755
- itemSubmissions: attempt.metadata.itemSubmissions,
104756
- assessment
104757
- }, input);
104758
- const scoring = transition.action === "commit" ? await this.scoreItem({
104759
- assessment,
104760
- responses: transition.responses,
104761
- itemIdentifier: input.itemIdentifier,
104762
- artifactIdentity: assessmentArtifactIdentityFromAttempt(attempt.metadata),
104763
- purpose: attempt.metadata.purpose
104764
- }) : null;
104765
- return {
104766
- responseVersion: attempt.metadata.responseVersion,
104767
- submissionId: input.submissionId,
104768
- itemIdentifier: input.itemIdentifier,
104769
- assessment,
104770
- scoring
104771
- };
104772
- }
104773
- async scoreSubmission(attempt) {
104774
- const assessment = await this.loadAttemptAssessment(attempt.metadata);
104775
- validateAssessmentResponses(assessment, attempt.metadata.responses);
104776
- const itemResults = attempt.metadata.purpose === "review" ? committedReviewItemResults(attempt.metadata, assessment) : await this.scoreItems({
104777
- assessment,
104778
- responses: attempt.metadata.responses,
104779
- artifactIdentity: assessmentArtifactIdentityFromAttempt(attempt.metadata),
104780
- purpose: attempt.metadata.purpose
104517
+ recordTranscriptFailure(operation, attemptId, error88) {
104518
+ addEvent("assessment.transcript_operation_failed", {
104519
+ "app.assessment.operation": operation,
104520
+ "app.assessment.attempt_id": attemptId,
104521
+ "exception.type": errorType(error88),
104522
+ "app.error.message": errorMessage(error88)
104781
104523
  });
104782
- return {
104783
- responseVersion: attempt.metadata.responseVersion,
104784
- assessment,
104785
- itemResults,
104786
- score: aggregateAssessmentScore(itemResults.map((result) => result.score.earned), itemResults.reduce((total, result) => total + result.score.possible, 0)),
104787
- correctQuestions: countCorrectQuestions(itemResults.map((result) => result.isCorrect))
104788
- };
104789
- }
104790
- async peekAttempt({
104791
- studentId,
104792
- attemptId
104793
- }) {
104794
- const result = await this.requireClient().api.oneroster.assessmentResults.get(attemptId).catch((error88) => {
104795
- if (isApiError(error88) && error88.statusCode === 404) {
104796
- throw this.unauthorizedAttempt(attemptId);
104797
- }
104798
- throw error88;
104799
- });
104800
- const metadata2 = playcademyAssessmentResultMetadata(result.metadata);
104801
- if (!metadata2 || result.student.sourcedId !== studentId || result.status !== ONEROSTER_STATUS2.active) {
104802
- throw this.unauthorizedAttempt(attemptId);
104803
- }
104804
- return { result, metadata: metadata2 };
104805
- }
104806
- async prepareSubmission(params, input) {
104807
- try {
104808
- const attempt = await this.peekAttempt(params);
104809
- const disposition = classifyAssessmentSubmission({
104810
- inProgress: attempt.result.inProgress ?? "",
104811
- scoreStatus: attempt.result.scoreStatus,
104812
- submissionId: attempt.metadata.submissionId
104813
- }, input.submissionId);
104814
- if (disposition !== "submit") {
104815
- return null;
104816
- }
104817
- if (this.isPlatformRoutedDiagnosticMetadata(attempt.metadata)) {
104818
- return null;
104819
- }
104820
- return await this.scoreSubmission(attempt);
104821
- } catch (error88) {
104822
- addEvent("assessment.submission_preview_failed", {
104823
- "app.assessment.attempt_id": params.attemptId,
104824
- "exception.type": errorType(error88),
104825
- "app.error.message": errorMessage(error88)
104826
- });
104827
- return null;
104828
- }
104829
104524
  }
104830
104525
  async submit({
104831
104526
  input,
@@ -104833,121 +104528,83 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
104833
104528
  ...params
104834
104529
  }) {
104835
104530
  await this.deps.validateDeveloperAccess(params.user, params.gameId);
104836
- const preview = await this.prepareSubmission(params, input);
104837
- const submission = await this.deps.assessmentRuntimeLock(this.deps.db, [assessmentRuntimeAttemptLockKey(params.attemptId)], async (db2) => {
104838
- const { attempt: identity, enrollment } = await this.resolveAttemptAuthorization(params, { db: db2, developerAccessValidated: true });
104839
- const submissionDisposition = classifyAssessmentSubmission({
104840
- inProgress: identity.result.inProgress ?? "",
104841
- scoreStatus: identity.result.scoreStatus,
104842
- submissionId: identity.metadata.submissionId
104843
- }, input.submissionId);
104844
- if (submissionDisposition === "replay") {
104531
+ const transcript = parseAssessmentTranscript(input.transcript);
104532
+ const request = { ...input, transcript };
104533
+ const requestHash = await assessmentTranscriptRequestHash(request);
104534
+ const accepted = await this.deps.assessmentRuntimeLock(this.deps.db, [assessmentRuntimeAttemptLockKey(params.attemptId)], async (db2) => {
104535
+ const { attempt } = await this.resolveAttemptAuthorization(params, {
104536
+ db: db2,
104537
+ developerAccessValidated: true,
104538
+ includeEnrollment: false
104539
+ });
104540
+ const disposition = resolveAssessmentTranscriptSubmission(attempt.result, attempt.metadata, attempt.integration.status, request, requestHash);
104541
+ if (disposition === "replay") {
104845
104542
  return {
104846
- response: this.submittedResult(identity.result, identity.metadata),
104847
- emission: this.replayCompletion(identity, input.submissionId, params, game2)
104543
+ response: this.submittedResult(attempt.result, attempt.metadata),
104544
+ emission: this.replayCompletion(attempt, input.submissionId, params, game2)
104848
104545
  };
104849
104546
  }
104850
- const attempt = this.requireActiveAttempt({ attempt: identity, enrollment }, params.attemptId);
104851
- if (submissionDisposition === "reject") {
104852
- throw AssessmentRuntimeError.from(assessmentAlreadySubmitted(attempt.result.sourcedId));
104853
- }
104854
- if (input.session && input.session.runId !== attempt.result.sourcedId) {
104855
- throw new ValidationError("Assessment session runId must match the attemptId.");
104856
- }
104857
- this.assertResponseVersion(attempt.metadata, input.expectedResponseVersion);
104858
- if (this.isPlatformRoutedDiagnosticMetadata(attempt.metadata)) {
104859
- return this.finalizeDiagnosticSubmission({
104860
- attempt: { ...attempt, metadata: attempt.metadata },
104861
- input,
104862
- params,
104863
- game: game2,
104864
- db: db2
104865
- });
104866
- }
104867
- if (assessmentFlowForPurpose(attempt.metadata.purpose) === "item-submit") {
104868
- const assessment2 = await this.loadAttemptAssessment(attempt.metadata);
104869
- const flowFailure = assessmentCompletionFlowFailure(attempt.metadata.purpose, attempt.metadata.itemSubmissions, assessment2.items.length);
104870
- if (flowFailure) {
104871
- throw AssessmentRuntimeError.from(flowFailure);
104872
- }
104873
- }
104874
- const scored = preview && preview.responseVersion === attempt.metadata.responseVersion ? preview : await this.scoreSubmission(attempt);
104875
- const { assessment, itemResults, score, correctQuestions } = scored;
104876
- const client2 = this.requireClient();
104547
+ const graded = await this.gradeTranscript({
104548
+ attemptId: params.attemptId,
104549
+ metadata: attempt.metadata
104550
+ }, transcript, true, db2);
104551
+ const diagnostic = this.isPlatformRoutedDiagnosticMetadata(graded.metadata) ? summarizeDiagnosticSubmission(graded.metadata, graded.manifest) : null;
104552
+ const itemOutcomes = graded.metadata.purpose === "review" ? assessmentReviewTranscriptOutcomes({
104553
+ metadata: graded.metadata,
104554
+ itemResults: graded.itemResults
104555
+ }) : undefined;
104556
+ const courseName = attempt.metadata.courseName ?? "Game Course";
104877
104557
  const timestamp6 = new Date().toISOString();
104878
- const reviewOutcomes = attempt.metadata.purpose === "review" ? this.reviewItemOutcomes({
104879
- metadata: attempt.metadata,
104880
- itemResults
104881
- }) : null;
104882
104558
  const finalization = buildAssessmentResultSubmission({
104883
104559
  result: attempt.result,
104884
- metadata: attempt.metadata,
104885
- score,
104560
+ metadata: graded.metadata,
104561
+ score: graded.score,
104886
104562
  submissionId: input.submissionId,
104887
104563
  timestamp: timestamp6,
104888
- testName: assessment.title,
104889
- courseName: attempt.enrollment.course.title,
104890
- totalQuestions: itemResults.length,
104891
- correctQuestions,
104564
+ testName: graded.assessment.title,
104565
+ courseName,
104566
+ totalQuestions: graded.itemResults.length,
104567
+ correctQuestions: graded.correctQuestions,
104892
104568
  ...input.session ? { session: input.session } : {},
104893
- ...reviewOutcomes ? { itemOutcomes: reviewOutcomes } : {}
104569
+ ...itemOutcomes ? { itemOutcomes } : {},
104570
+ ...diagnostic ? { diagnosticTracks: diagnostic.tracks } : {}
104894
104571
  });
104895
- const metadata2 = finalization.metadata;
104896
- const submitted = await client2.api.oneroster.assessmentResults.upsert(attempt.result.sourcedId, finalization.resultUpdate);
104897
- const submittedForEmission = {
104898
- ...submitted,
104899
- score: score.normalized * 100,
104900
- scoreDate: timestamp6,
104901
- ...ASSESSMENT_ATTEMPT_AWAITING_AWARD,
104902
- metadata: finalization.resultUpdate.metadata
104572
+ const metadata2 = {
104573
+ ...finalization.metadata,
104574
+ completion: { ...finalization.metadata.completion, requestHash }
104575
+ };
104576
+ const resultUpdate = {
104577
+ ...finalization.resultUpdate,
104578
+ metadata: mergeAssessmentRuntimeMetadata(attempt.result.metadata, metadata2)
104579
+ };
104580
+ await this.requireClient().api.oneroster.assessmentResults.upsert(params.attemptId, resultUpdate);
104581
+ const result = { ...attempt.result, ...resultUpdate };
104582
+ const emission = {
104583
+ attempt: { ...attempt, result, metadata: metadata2 },
104584
+ testIdentifier: graded.assessment.identifier,
104585
+ testName: graded.assessment.title,
104586
+ score: graded.score,
104587
+ totalQuestions: graded.itemResults.length,
104588
+ correctQuestions: graded.correctQuestions,
104589
+ submissionId: input.submissionId,
104590
+ gameId: params.gameId,
104591
+ studentId: params.studentId,
104592
+ courseName,
104593
+ game: game2
104903
104594
  };
104904
- addEvent("assessment.awaiting_award_created", {
104905
- "app.assessment.attempt_id": submittedForEmission.sourcedId,
104906
- "app.assessment.submission_id": input.submissionId,
104907
- "app.assessment.purpose": metadata2.purpose,
104908
- "app.assessment.response_version": metadata2.responseVersion
104909
- });
104910
104595
  return {
104911
- response: {
104912
- attemptId: submittedForEmission.sourcedId,
104913
- submissionId: input.submissionId,
104914
- responseVersion: metadata2.responseVersion,
104915
- status: "awaiting_award",
104916
- responses: metadata2.responses,
104917
- score,
104918
- ...metadata2.purpose === "review" ? {
104919
- purpose: metadata2.purpose,
104920
- itemOutcomes: reviewOutcomes
104921
- } : { purpose: metadata2.purpose }
104922
- },
104923
- emission: {
104924
- attempt: { ...attempt, result: submittedForEmission, metadata: metadata2 },
104925
- testIdentifier: assessment.identifier,
104926
- testName: assessment.title,
104927
- score,
104928
- totalQuestions: itemResults.length,
104929
- correctQuestions,
104930
- submissionId: input.submissionId,
104931
- gameId: params.gameId,
104932
- studentId: params.studentId,
104933
- courseName: attempt.enrollment.course.title,
104934
- game: game2
104935
- }
104596
+ response: this.submittedResult(result, metadata2),
104597
+ emission,
104598
+ repair: { context: this.tokenContext(attempt), graded }
104936
104599
  };
104937
104600
  });
104938
- if (submission.emission) {
104939
- const diagnosticCompletion = this.isPlatformRoutedDiagnosticMetadata(submission.emission.attempt.metadata) ? {
104940
- ...submission.emission.attempt,
104941
- metadata: submission.emission.attempt.metadata
104942
- } : null;
104943
- const { result, metadata: metadata2 } = submission.emission.attempt;
104944
- await Promise.allSettled([
104945
- this.emitSessionBestEffort(submission.emission),
104946
- ...diagnosticCompletion ? [this.restoreDiagnosticChildResults(diagnosticCompletion)] : [],
104947
- ...metadata2.purpose === "review" ? [this.restoreAllCompletedReviewChildResponses(result, metadata2)] : []
104948
- ]);
104601
+ if (accepted.repair) {
104602
+ this.reconcileTranscriptChildren(accepted.repair.context, accepted.repair.graded).catch((error88) => this.recordTranscriptFailure("completion_repair", params.attemptId, error88));
104603
+ }
104604
+ if (accepted.emission) {
104605
+ this.emitSessionBestEffort(accepted.emission);
104949
104606
  }
104950
- return submission.response;
104607
+ return accepted.response;
104951
104608
  }
104952
104609
  async finalize({
104953
104610
  input,
@@ -105091,58 +104748,6 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
105091
104748
  });
105092
104749
  }
105093
104750
  }
105094
- async finalizeDiagnosticSubmission(input) {
105095
- const { attempt } = input;
105096
- const routing = await this.loadAttemptDiagnosticManifest(attempt.metadata, input.db);
105097
- const { tracks, score, correctQuestions } = summarizeDiagnosticSubmission(attempt.metadata, routing.manifest);
105098
- const timestamp6 = new Date().toISOString();
105099
- const finalization = buildAssessmentResultSubmission({
105100
- result: attempt.result,
105101
- metadata: attempt.metadata,
105102
- score,
105103
- submissionId: input.input.submissionId,
105104
- timestamp: timestamp6,
105105
- testName: attempt.metadata.diagnostic.testName,
105106
- courseName: attempt.enrollment.course.title,
105107
- totalQuestions: attempt.metadata.itemSubmissions.length,
105108
- correctQuestions,
105109
- ...input.input.session ? { session: input.input.session } : {},
105110
- diagnosticTracks: tracks
105111
- });
105112
- const metadata2 = finalization.metadata;
105113
- const submitted = await this.requireClient().api.oneroster.assessmentResults.upsert(attempt.result.sourcedId, finalization.resultUpdate);
105114
- const submittedForEmission = {
105115
- ...submitted,
105116
- score: score.normalized * 100,
105117
- scoreDate: timestamp6,
105118
- ...ASSESSMENT_ATTEMPT_AWAITING_AWARD,
105119
- metadata: finalization.resultUpdate.metadata
105120
- };
105121
- return {
105122
- response: {
105123
- purpose: "diagnostic",
105124
- attemptId: submittedForEmission.sourcedId,
105125
- submissionId: input.input.submissionId,
105126
- diagnosticKey: metadata2.diagnostic.diagnosticKey,
105127
- responseVersion: metadata2.responseVersion,
105128
- status: "awaiting_award",
105129
- tracks
105130
- },
105131
- emission: {
105132
- attempt: { ...attempt, result: submittedForEmission, metadata: metadata2 },
105133
- testIdentifier: attempt.metadata.selectedTest.identifier,
105134
- testName: attempt.metadata.diagnostic.testName,
105135
- score,
105136
- totalQuestions: attempt.metadata.itemSubmissions.length,
105137
- correctQuestions,
105138
- submissionId: input.input.submissionId,
105139
- gameId: input.params.gameId,
105140
- studentId: input.params.studentId,
105141
- courseName: attempt.enrollment.course.title,
105142
- game: input.game
105143
- }
105144
- };
105145
- }
105146
104751
  async exportFixture({
105147
104752
  gameId,
105148
104753
  user
@@ -105342,14 +104947,32 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
105342
104947
  async listAttempts(scope) {
105343
104948
  return this.attemptListing(await this.studentAssessmentResults(scope.studentId), scope);
105344
104949
  }
105345
- attemptListing(results, scope) {
104950
+ async attemptListing(results, scope) {
105346
104951
  const attempts = new Map;
105347
104952
  const reviewChildren = new Map;
105348
104953
  for (const result of results) {
105349
104954
  const metadata2 = playcademyAssessmentResultMetadata(result.metadata);
105350
- const childMetadata = scope.purpose === "review" ? playcademyAssessmentItemResultMetadata(result.metadata) : null;
105351
- if (childMetadata && childMetadata.activityId === scope.activityId && childMetadata.integrationId === scope.integrationId && childMetadata.enrollmentId === scope.enrollmentId && result.student.sourcedId === scope.studentId) {
105352
- reviewChildren.set(result.sourcedId, result);
104955
+ if (scope.purpose === "review") {
104956
+ const verified = await verifyAssessmentChildResultCommand(result.metadata);
104957
+ if (verified.ok && verified.command.payload.context.kind === "review") {
104958
+ const { command } = verified;
104959
+ const { attempt, administration } = command.payload;
104960
+ const parent = await reviewQuestionLineItemId(await deterministicUUID([
104961
+ "playcademy:review-bank-line-item:v1",
104962
+ scope.integrationId,
104963
+ scope.activityId,
104964
+ attempt.selectedTest.identifier,
104965
+ verified.command.payload.context.bankRevision
104966
+ ].join("\x00")), administration.itemIdentifier);
104967
+ const projection = await classifyAssessmentChildResult({
104968
+ result,
104969
+ command,
104970
+ childLineItemId: parent
104971
+ });
104972
+ if (attempt.activityId === scope.activityId && attempt.integrationId === scope.integrationId && attempt.enrollmentId === scope.enrollmentId && attempt.courseId === scope.courseId && attempt.studentId === scope.studentId && result.sourcedId === await assessmentChildResultId(attempt.attemptId, administration.submissionId) && projection.outcome === "replay") {
104973
+ reviewChildren.set(result.sourcedId, command);
104974
+ }
104975
+ }
105353
104976
  }
105354
104977
  if (metadata2 && metadata2.activityId === scope.activityId && metadata2.purpose === scope.purpose && metadata2.courseId === scope.courseId && metadata2.integrationId === scope.integrationId && result.student.sourcedId === scope.studentId) {
105355
104978
  const selectedAssessmentKey = metadata2.selectedTest.assessmentKey ?? assessmentKeyFromManagedQtiIdentifier(metadata2.selectedTest.identifier);
@@ -105400,19 +105023,6 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
105400
105023
  }
105401
105024
  return true;
105402
105025
  }
105403
- reviewExposures(results, scope) {
105404
- const exposures = [];
105405
- for (const result of results) {
105406
- const metadata2 = playcademyAssessmentItemResultMetadata(result.metadata);
105407
- if (metadata2?.bankRevision === scope.bankRevision) {
105408
- exposures.push({
105409
- itemIdentifier: metadata2.itemIdentifier,
105410
- deliveredAt: metadata2.deliveredAt
105411
- });
105412
- }
105413
- }
105414
- return exposures;
105415
- }
105416
105026
  requireDecision(decision, purpose) {
105417
105027
  if (!decision) {
105418
105028
  throw AssessmentRuntimeError.from(assessmentNoEligibleTests(purpose));
@@ -105429,29 +105039,9 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
105429
105039
  const resumed = attempts.get(decision.attempt.attemptId);
105430
105040
  return this.snapshotForResult(resumed.result, resumed.metadata, integration);
105431
105041
  }
105432
- async resumeDiagnosticSnapshot(decision, attempts, integration, db2 = this.deps.db) {
105433
- if (decision.additionalResumableAttemptIds.length > 0) {
105434
- addEvent("assessment.multiple_resumable_attempts", {
105435
- "app.assessment.attempt_id": decision.attempt.attemptId,
105436
- "app.assessment.additional_attempt_ids": decision.additionalResumableAttemptIds.join(",")
105437
- });
105438
- }
105042
+ async resumeDiagnosticSnapshot(decision, attempts, integration, _db = this.deps.db) {
105439
105043
  const resumed = attempts.get(decision.attempt.attemptId);
105440
- if (this.isSettled(resumed.result)) {
105441
- return this.snapshotForResult(resumed.result, resumed.metadata, integration);
105442
- }
105443
- if (!this.isPlatformRoutedDiagnosticMetadata(resumed.metadata)) {
105444
- throw new Error("A keyed diagnostic request selected a legacy fixed attempt");
105445
- }
105446
- const assessment = await this.loadAttemptAssessment(resumed.metadata);
105447
- const routing = await this.loadAttemptDiagnosticRouting(resumed.metadata, assessment, db2);
105448
- await this.restoreDiagnosticChildResults({
105449
- result: resumed.result,
105450
- metadata: resumed.metadata,
105451
- integration,
105452
- assessment
105453
- });
105454
- return this.diagnosticSnapshot(resumed.result, resumed.metadata, assessment, integration, routing.state);
105044
+ return this.snapshotForResult(resumed.result, resumed.metadata, integration);
105455
105045
  }
105456
105046
  async resumeAvailableAttempt(tests, attempts, requestFingerprint, pendingSupersessions, resume) {
105457
105047
  const select3 = () => selectRuntimeAssessment(tests, [...this.attemptsMatchingRequest(attempts, requestFingerprint).values()].map((entry) => entry.selection));
@@ -105522,42 +105112,9 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
105522
105112
  });
105523
105113
  }
105524
105114
  }
105525
- async resumeReviewSnapshot(decision, attempts, existingChildResults, context2) {
105526
- if (decision.additionalResumableAttemptIds.length > 0) {
105527
- addEvent("assessment.multiple_resumable_attempts", {
105528
- "app.assessment.attempt_id": decision.attempt.attemptId,
105529
- "app.assessment.additional_attempt_ids": decision.additionalResumableAttemptIds.join(",")
105530
- });
105531
- }
105115
+ async resumeReviewSnapshot(decision, attempts, context2) {
105532
105116
  const resumed = attempts.get(decision.attempt.attemptId);
105533
- if (resumed.metadata.purpose !== "review") {
105534
- throw new Error("A standards-review request selected a non-review attempt");
105535
- }
105536
- const reviewMetadata = resumed.metadata;
105537
- let assessment;
105538
- const current = await reconcileReviewChildResults({
105539
- initial: resumed,
105540
- writeOpen: async () => {
105541
- const source = await this.loadPinnedReviewBankSource(reviewMetadata);
105542
- assessment = projectReviewAssessment(source.assessment, source.bank, reviewMetadata.review.selections);
105543
- await this.ensureReviewChildResults({
105544
- result: resumed.result,
105545
- metadata: reviewMetadata,
105546
- integration: context2.integration,
105547
- source,
105548
- assessment,
105549
- existingChildResults,
105550
- lookupMissingResults: true
105551
- });
105552
- },
105553
- crossBarrier: () => crossAssessmentAttemptLockBarrier(this.deps.assessmentRuntimeLock, this.deps.db, resumed.result.sourcedId),
105554
- readLatest: () => this.peekAttempt({
105555
- studentId: resumed.result.student.sourcedId,
105556
- attemptId: resumed.result.sourcedId
105557
- }),
105558
- restoreTerminal: (latest) => this.restoreAllCompletedReviewChildResponses(latest.result, latest.metadata)
105559
- });
105560
- return assessmentChildProjectionIsTerminal(current.result) ? this.snapshotForResult(current.result, current.metadata, context2.integration) : this.snapshot(current.result, current.metadata, assessment, context2.integration);
105117
+ return this.snapshotForResult(resumed.result, resumed.metadata, context2.integration);
105561
105118
  }
105562
105119
  recordReviewPreparationPhase(phase, startedAt, counts = {}) {
105563
105120
  addEvent("assessment.review_preparation_phase", {
@@ -105965,135 +105522,82 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
105965
105522
  }
105966
105523
  return lineItemId;
105967
105524
  }
105968
- assessmentChildAttempt(attempt) {
105969
- const { metadata: metadata2 } = attempt;
105970
- const assessmentKey = metadata2.selectedTest.assessmentKey ?? assessmentKeyFromManagedQtiIdentifier(metadata2.selectedTest.identifier);
105971
- if (!assessmentKey) {
105972
- addEvent("assessment.child_result_legacy_attempt_skipped", {
105973
- "app.assessment.attempt_id": attempt.result.sourcedId
105974
- });
105975
- return null;
105976
- }
105525
+ tokenContext(attempt) {
105977
105526
  return {
105978
- attemptId: attempt.result.sourcedId,
105527
+ version: 1,
105979
105528
  gameId: attempt.integration.gameId,
105980
105529
  studentId: attempt.result.student.sourcedId,
105981
- activityId: metadata2.activityId,
105982
- courseId: metadata2.courseId,
105983
- integrationId: metadata2.integrationId,
105984
- enrollmentId: metadata2.enrollmentId,
105985
- selectedTest: {
105986
- assessmentKey,
105987
- identifier: metadata2.selectedTest.identifier,
105988
- contentRevision: metadata2.selectedTest.contentRevision
105989
- }
105990
- };
105991
- }
105992
- reviewChildResultContext(metadata2, submission) {
105993
- const selection = metadata2.review.selections.find((candidate) => candidate.itemIdentifier === submission.itemIdentifier);
105994
- if (!selection) {
105995
- throw new Error(`Committed review item ${submission.itemIdentifier} is not in its pinned selection`);
105996
- }
105997
- return {
105998
- kind: "review",
105999
- bankRevision: metadata2.review.bankRevision,
106000
- standard: selection.standard
105530
+ attemptId: attempt.result.sourcedId,
105531
+ parentLineItemId: attempt.result.assessmentLineItem.sourcedId,
105532
+ metadata: assessmentTokenMetadata(attempt.metadata)
106001
105533
  };
106002
105534
  }
106003
- prepareReviewAssessmentChildResult(input) {
106004
- return this.prepareAssessmentChildResult({
106005
- attempt: input.attempt,
106006
- metadata: input.metadata,
106007
- submission: input.submission,
106008
- context: () => this.reviewChildResultContext(input.metadata, input.submission)
105535
+ async ensureTranscriptLineItems(context2, assessment) {
105536
+ await runWithConcurrency(assessment.items, TimebackAssessmentRuntimeService.SCORING_CONCURRENCY, async (item) => {
105537
+ const id = await assessmentTranscriptChildLineItemId(context2, item.identifier);
105538
+ if (this.childLineItemCache.get(id)) {
105539
+ return;
105540
+ }
105541
+ const metadata2 = context2.metadata;
105542
+ const common2 = {
105543
+ parentLineItemId: context2.parentLineItemId,
105544
+ integration: { courseId: metadata2.courseId },
105545
+ item
105546
+ };
105547
+ if (metadata2.purpose === "review") {
105548
+ const standard = metadata2.review.selections.find((selection) => selection.itemIdentifier === item.identifier)?.standard;
105549
+ if (!standard) {
105550
+ throw new Error("Review candidate is not in the pinned selection");
105551
+ }
105552
+ await this.ensureReviewQuestionLineItem({
105553
+ ...common2,
105554
+ bankRevision: metadata2.review.bankRevision,
105555
+ standards: [standard]
105556
+ });
105557
+ } else if (this.isPlatformRoutedDiagnosticMetadata(metadata2)) {
105558
+ await this.ensureDiagnosticQuestionLineItem({ ...common2, metadata: metadata2 });
105559
+ } else {
105560
+ const expected = {
105561
+ status: "active",
105562
+ parentAssessmentLineItem: { sourcedId: context2.parentLineItemId },
105563
+ course: { sourcedId: metadata2.courseId },
105564
+ resultValueMin: 0,
105565
+ resultValueMax: item.maxScore,
105566
+ metadata: {
105567
+ playcademyAssessmentQuestionDefinition: {
105568
+ version: 1,
105569
+ itemIdentifier: item.identifier,
105570
+ contentRevision: metadata2.selectedTest.contentRevision
105571
+ }
105572
+ }
105573
+ };
105574
+ const store = this.requireClient().api.oneroster.assessmentLineItems;
105575
+ try {
105576
+ await store.create({ sourcedId: id, title: item.title, ...expected });
105577
+ } catch (error88) {
105578
+ if (!isApiError(error88) || error88.statusCode !== 409) {
105579
+ throw error88;
105580
+ }
105581
+ this.assertAssessmentChildLineItem(id, await store.get(id), expected);
105582
+ }
105583
+ }
105584
+ this.childLineItemCache.set(id, true);
106009
105585
  });
106010
105586
  }
106011
- prepareDiagnosticAssessmentChildResult(input) {
106012
- return this.prepareAssessmentChildResult({
106013
- attempt: input.attempt,
106014
- metadata: input.metadata,
106015
- submission: input.submission,
106016
- context: () => ({
106017
- kind: "platform-routed-diagnostic",
106018
- definitionId: input.metadata.diagnostic.definitionId,
106019
- diagnosticKey: input.metadata.diagnostic.diagnosticKey,
106020
- routingRevision: input.metadata.diagnostic.routingRevision,
106021
- transition: input.transition
105587
+ async reconcileTranscriptChildren(context2, graded, only) {
105588
+ return reconcileAssessmentTranscriptChildren({
105589
+ context: context2,
105590
+ graded,
105591
+ only,
105592
+ assessmentResults: this.requireClient().api.oneroster.assessmentResults,
105593
+ ensureLineItems: (assessment) => this.ensureTranscriptLineItems(context2, assessment),
105594
+ onPersisted: (submissionId) => addEvent("assessment.child_result_persisted", {
105595
+ "app.assessment.attempt_id": context2.attemptId,
105596
+ "app.assessment.submission_id": submissionId,
105597
+ "app.assessment.child_result_outcome": "upserted"
106022
105598
  })
106023
105599
  });
106024
105600
  }
106025
- async prepareAssessmentChildResult(input) {
106026
- try {
106027
- const attemptContext = this.assessmentChildAttempt({
106028
- ...input.attempt,
106029
- metadata: input.metadata
106030
- });
106031
- if (!attemptContext) {
106032
- return null;
106033
- }
106034
- const parentLineItemId = input.attempt.result.assessmentLineItem.sourcedId;
106035
- const { itemIdentifier } = input.submission;
106036
- const context2 = input.context();
106037
- const [command, childLineItemId] = await Promise.all([
106038
- buildSubmissionChildResultCommand({
106039
- attempt: attemptContext,
106040
- submission: input.submission,
106041
- responses: input.metadata.responses[itemIdentifier],
106042
- grading: input.submission.grading ?? QTI_CHILD_GRADING,
106043
- context: context2
106044
- }),
106045
- context2.kind === "review" ? reviewQuestionLineItemId(parentLineItemId, itemIdentifier) : diagnosticQuestionLineItemId(parentLineItemId, itemIdentifier)
106046
- ]);
106047
- return { command, childLineItemId };
106048
- } catch (error88) {
106049
- addEvent("assessment.child_result_prepare_failed", {
106050
- "app.assessment.attempt_id": input.attempt.result.sourcedId,
106051
- "app.assessment.submission_id": input.submission.submissionId,
106052
- "app.assessment.qti_item_identifier": input.submission.itemIdentifier,
106053
- "exception.type": errorType(error88),
106054
- "app.error.message": errorMessage(error88)
106055
- });
106056
- return null;
106057
- }
106058
- }
106059
- async projectPreparedAssessmentChildResult(prepared) {
106060
- if (!prepared) {
106061
- return;
106062
- }
106063
- const startedAt = Date.now();
106064
- const identity = {
106065
- "app.assessment.attempt_id": prepared.command.payload.attempt.attemptId,
106066
- "app.assessment.submission_id": prepared.command.payload.administration.submissionId
106067
- };
106068
- try {
106069
- const disposition = await persistAssessmentChildResult({
106070
- assessmentResults: this.requireClient().api.oneroster.assessmentResults,
106071
- command: prepared.command,
106072
- childLineItemId: prepared.childLineItemId
106073
- });
106074
- if (disposition.outcome === "conflict") {
106075
- addEvent("assessment.child_result_conflict", {
106076
- ...identity,
106077
- "app.assessment.child_result_id": disposition.resultId,
106078
- "app.assessment.child_result_conflict_reason": disposition.reason
106079
- });
106080
- return;
106081
- }
106082
- addEvent("assessment.child_result_persisted", {
106083
- ...identity,
106084
- "app.assessment.child_result_id": disposition.resultId,
106085
- "app.assessment.child_result_outcome": disposition.outcome,
106086
- "app.assessment.duration_ms": Date.now() - startedAt
106087
- });
106088
- } catch (error88) {
106089
- addEvent("assessment.child_result_persist_failed", {
106090
- ...identity,
106091
- "app.assessment.duration_ms": Date.now() - startedAt,
106092
- "exception.type": errorType(error88),
106093
- "app.error.message": errorMessage(error88)
106094
- });
106095
- }
106096
- }
106097
105601
  assertAssessmentChildLineItem(lineItemId, stored, expected) {
106098
105602
  if (assessmentChildLineItemMatches(stored, expected)) {
106099
105603
  return;
@@ -106151,91 +105655,6 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
106151
105655
  }
106152
105656
  return lineItemId;
106153
105657
  }
106154
- async projectDiagnosticItemResponse(projection, assessment, checkpoint = null) {
106155
- try {
106156
- const loadedAssessment = assessment ?? await this.loadAttemptAssessment(projection.metadata);
106157
- const item = loadedAssessment.items.find((candidate) => candidate.identifier === projection.ledgerEntry.itemIdentifier);
106158
- if (!item) {
106159
- throw new Error(`Diagnostic assessment is missing item ${projection.ledgerEntry.itemIdentifier}`);
106160
- }
106161
- const childLineItemId = await this.ensureDiagnosticQuestionLineItem({
106162
- parentLineItemId: projection.result.assessmentLineItem.sourcedId,
106163
- integration: projection.integration,
106164
- metadata: projection.metadata,
106165
- item
106166
- });
106167
- if (checkpoint && checkpoint.childLineItemId !== childLineItemId) {
106168
- throw new Error("Prepared diagnostic checkpoint resolved a different line item");
106169
- }
106170
- const childResultId = await diagnosticQuestionResultId(projection.result.sourcedId, childLineItemId);
106171
- const finalized = buildFinalizedDiagnosticChildResult({
106172
- childLineItemId,
106173
- student: projection.result.student,
106174
- parentAttemptId: projection.result.sourcedId,
106175
- metadata: projection.metadata,
106176
- ledgerEntry: projection.ledgerEntry
106177
- });
106178
- await Promise.all([
106179
- this.projectPreparedAssessmentChildResult(checkpoint),
106180
- this.requireClient().api.oneroster.assessmentResults.upsert(childResultId, finalized.resultUpdate)
106181
- ]);
106182
- } catch (error88) {
106183
- addEvent("assessment.diagnostic_child_projection_failed", {
106184
- "app.assessment.attempt_id": projection.result.sourcedId,
106185
- "app.assessment.qti_item_identifier": projection.ledgerEntry.itemIdentifier,
106186
- "exception.type": errorType(error88),
106187
- "app.error.message": errorMessage(error88)
106188
- });
106189
- }
106190
- }
106191
- async restoreDiagnosticChildResults(attempt) {
106192
- const assessment = attempt.assessment ?? await this.loadAttemptAssessment(attempt.metadata);
106193
- await runWithConcurrency(attempt.metadata.diagnostic.ledger, TimebackAssessmentRuntimeService.SCORING_CONCURRENCY, async (ledgerEntry) => {
106194
- try {
106195
- const childLineItemId = await diagnosticQuestionLineItemId(attempt.result.assessmentLineItem.sourcedId, ledgerEntry.itemIdentifier);
106196
- const childResultId = await diagnosticQuestionResultId(attempt.result.sourcedId, childLineItemId);
106197
- let childResult;
106198
- try {
106199
- childResult = await this.requireClient().api.oneroster.assessmentResults.get(childResultId);
106200
- } catch (error88) {
106201
- if (!isApiError(error88) || error88.statusCode !== 404) {
106202
- throw error88;
106203
- }
106204
- }
106205
- const resolution = resolveDiagnosticChildResult({
106206
- childResult,
106207
- childLineItemId,
106208
- parentResult: attempt.result,
106209
- parentMetadata: attempt.metadata,
106210
- ledgerEntry
106211
- });
106212
- if (resolution.action === "reuse") {
106213
- return;
106214
- }
106215
- if (resolution.action === "restore") {
106216
- addEvent("assessment.diagnostic_child_result_restore", {
106217
- "app.assessment.diagnostic_child_restore_reason": resolution.reason,
106218
- "app.assessment.attempt_id": attempt.result.sourcedId,
106219
- "app.assessment.child_result_id": childResultId,
106220
- "app.assessment.qti_item_identifier": ledgerEntry.itemIdentifier
106221
- });
106222
- }
106223
- await this.projectDiagnosticItemResponse({
106224
- result: attempt.result,
106225
- metadata: attempt.metadata,
106226
- ledgerEntry,
106227
- integration: attempt.integration
106228
- }, assessment);
106229
- } catch (error88) {
106230
- addEvent("assessment.diagnostic_child_reconciliation_failed", {
106231
- "app.assessment.attempt_id": attempt.result.sourcedId,
106232
- "app.assessment.qti_item_identifier": ledgerEntry.itemIdentifier,
106233
- "exception.type": errorType(error88),
106234
- "app.error.message": errorMessage(error88)
106235
- });
106236
- }
106237
- });
106238
- }
106239
105658
  async ensureReviewQuestionLineItem(input) {
106240
105659
  const client2 = this.requireClient();
106241
105660
  const lineItemId = await reviewQuestionLineItemId(input.parentLineItemId, input.item.identifier);
@@ -106282,220 +105701,6 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
106282
105701
  }
106283
105702
  return lineItemId;
106284
105703
  }
106285
- async putOpenReviewChildResult(input) {
106286
- const metadata2 = buildReviewItemResultMetadata({
106287
- metadata: input.parentMetadata,
106288
- selection: input.selection,
106289
- parentAttemptId: input.parentResult.sourcedId
106290
- });
106291
- await this.requireClient().api.oneroster.assessmentResults.upsert(input.childResultId, buildOpenReviewChildResult({
106292
- childLineItemId: input.childLineItemId,
106293
- student: input.parentResult.student,
106294
- metadata: metadata2
106295
- }));
106296
- }
106297
- async ensureReviewChildResults(input) {
106298
- const itemByIdentifier = new Map(input.assessment.items.map((item) => [item.identifier, item]));
106299
- const bankItemByIdentifier = new Map(input.source.bank.items.map((item) => [item.itemIdentifier, item]));
106300
- const physicalItems = new Set(input.metadata.review.selections.map((selection) => selection.itemIdentifier));
106301
- if (physicalItems.size !== input.metadata.review.selections.length) {
106302
- throw new Error("MVP review selection must use each physical item at most once");
106303
- }
106304
- const administeredItems = administeredItemIdentifiers(input.metadata.itemSubmissions);
106305
- await runWithConcurrency(input.metadata.review.selections, TimebackAssessmentRuntimeService.SCORING_CONCURRENCY, async (selection) => {
106306
- const item = itemByIdentifier.get(selection.itemIdentifier);
106307
- const indexed = bankItemByIdentifier.get(selection.itemIdentifier);
106308
- if (!item || !indexed) {
106309
- throw new Error(`Review assessment or bank is missing selected item ${selection.itemIdentifier}`);
106310
- }
106311
- const childLineItemId = await this.ensureReviewQuestionLineItem({
106312
- parentLineItemId: input.result.assessmentLineItem.sourcedId,
106313
- integration: input.integration,
106314
- bankRevision: input.metadata.review.bankRevision,
106315
- standards: [...indexed.standards],
106316
- item
106317
- });
106318
- if (!administeredItems.has(selection.itemIdentifier)) {
106319
- return;
106320
- }
106321
- const childResultId = await reviewQuestionResultId(input.result.sourcedId, childLineItemId);
106322
- const streamedChildResult = input.existingChildResults.get(childResultId);
106323
- let childResult = streamedChildResult;
106324
- if (!childResult && input.lookupMissingResults) {
106325
- try {
106326
- childResult = await this.requireClient().api.oneroster.assessmentResults.get(childResultId);
106327
- } catch (error88) {
106328
- if (!isApiError(error88) || error88.statusCode !== 404) {
106329
- throw error88;
106330
- }
106331
- }
106332
- }
106333
- const resolution = resolveReviewChildResult({
106334
- childResult,
106335
- childLineItemId,
106336
- parentResult: input.result,
106337
- parentMetadata: input.metadata,
106338
- selection
106339
- });
106340
- if (resolution.action === "reuse") {
106341
- return;
106342
- }
106343
- if (resolution.action === "restore") {
106344
- addEvent("assessment.review_child_result_restore", {
106345
- "app.assessment.review_child_restore_reason": resolution.reason,
106346
- "app.assessment.attempt_id": input.result.sourcedId,
106347
- "app.assessment.child_result_id": childResultId,
106348
- "app.assessment.qti_item_identifier": selection.itemIdentifier
106349
- });
106350
- }
106351
- await this.putOpenReviewChildResult({
106352
- childResultId,
106353
- childLineItemId,
106354
- parentResult: input.result,
106355
- parentMetadata: input.metadata,
106356
- selection
106357
- });
106358
- });
106359
- }
106360
- async putReviewChildResponse(projection, checkpoint) {
106361
- const { result, metadata: metadata2, itemIdentifier, integration } = projection;
106362
- const selection = metadata2.review.selections.find((candidate) => candidate.itemIdentifier === itemIdentifier);
106363
- if (!selection) {
106364
- return;
106365
- }
106366
- try {
106367
- const source = await this.loadPinnedReviewBankSource(metadata2);
106368
- const item = source.assessment.items.find((candidate) => candidate.identifier === selection.itemIdentifier);
106369
- const indexed = source.bank.items.find((candidate) => candidate.itemIdentifier === selection.itemIdentifier);
106370
- if (!item || !indexed) {
106371
- throw new Error(`Review assessment or bank is missing selected item ${selection.itemIdentifier}`);
106372
- }
106373
- const childLineItemId = await this.ensureReviewQuestionLineItem({
106374
- parentLineItemId: result.assessmentLineItem.sourcedId,
106375
- integration,
106376
- bankRevision: metadata2.review.bankRevision,
106377
- standards: [...indexed.standards],
106378
- item
106379
- });
106380
- if (checkpoint && checkpoint.childLineItemId !== childLineItemId) {
106381
- throw new Error("Prepared review checkpoint resolved a different line item");
106382
- }
106383
- const childResultId = await reviewQuestionResultId(result.sourcedId, childLineItemId);
106384
- const childMetadata = buildReviewItemResultMetadata({
106385
- metadata: metadata2,
106386
- selection,
106387
- parentAttemptId: result.sourcedId
106388
- });
106389
- await Promise.all([
106390
- this.projectPreparedAssessmentChildResult(checkpoint),
106391
- this.requireClient().api.oneroster.assessmentResults.upsert(childResultId, buildOpenReviewChildResult({
106392
- childLineItemId,
106393
- student: result.student,
106394
- metadata: childMetadata
106395
- }))
106396
- ]);
106397
- } catch (error88) {
106398
- addEvent("assessment.review_child_response_projection_failed", {
106399
- "app.assessment.attempt_id": result.sourcedId,
106400
- "app.assessment.qti_item_identifier": selection.itemIdentifier,
106401
- "exception.type": errorType(error88),
106402
- "app.error.message": errorMessage(error88)
106403
- });
106404
- }
106405
- }
106406
- async projectReviewItemResponse(params, projection, checkpoint) {
106407
- const terminal = assessmentChildProjectionIsTerminal(projection.result);
106408
- try {
106409
- await reconcileReviewChildResults({
106410
- initial: projection,
106411
- writeOpen: () => this.putReviewChildResponse(projection, checkpoint),
106412
- crossBarrier: () => crossAssessmentAttemptLockBarrier(this.deps.assessmentRuntimeLock, this.deps.db, params.attemptId),
106413
- readLatest: () => this.peekAttempt(params),
106414
- restoreTerminal: async (latest) => {
106415
- const repair = this.restoreCompletedReviewChildResponses(latest.result, latest.metadata, new Set([projection.itemIdentifier]));
106416
- if (terminal) {
106417
- await Promise.allSettled([
106418
- this.projectPreparedAssessmentChildResult(checkpoint),
106419
- repair
106420
- ]);
106421
- } else {
106422
- await repair;
106423
- }
106424
- }
106425
- });
106426
- } catch (error88) {
106427
- addEvent("assessment.review_child_projection_barrier_failed", {
106428
- "app.assessment.attempt_id": params.attemptId,
106429
- "app.assessment.qti_item_identifier": projection.itemIdentifier,
106430
- "exception.type": errorType(error88),
106431
- "app.error.message": errorMessage(error88)
106432
- });
106433
- }
106434
- }
106435
- restoreAllCompletedReviewChildResponses(result, metadata2) {
106436
- return this.restoreCompletedReviewChildResponses(result, metadata2, new Set(metadata2.itemSubmissions.map((item) => item.itemIdentifier)));
106437
- }
106438
- async restoreCompletedReviewChildResponses(result, metadata2, changedItemIdentifiers) {
106439
- const outcomes = metadata2.completion?.itemOutcomes;
106440
- const submissionId = metadata2.submissionId;
106441
- if (!submissionId || !outcomes) {
106442
- addEvent("assessment.review_child_terminal_restore_unavailable", {
106443
- "app.assessment.attempt_id": result.sourcedId
106444
- });
106445
- return;
106446
- }
106447
- const outcomeByItem = new Map(outcomes.map((outcome) => [outcome.itemIdentifier, outcome]));
106448
- await runWithConcurrency(metadata2.review.selections.filter((selection) => changedItemIdentifiers.has(selection.itemIdentifier)), TimebackAssessmentRuntimeService.SCORING_CONCURRENCY, async (selection) => {
106449
- try {
106450
- const outcome = outcomeByItem.get(selection.itemIdentifier);
106451
- if (!outcome) {
106452
- throw new Error(`Completed review omitted item ${selection.itemIdentifier}`);
106453
- }
106454
- const childLineItemId = await reviewQuestionLineItemId(result.assessmentLineItem.sourcedId, selection.itemIdentifier);
106455
- const childResultId = await reviewQuestionResultId(result.sourcedId, childLineItemId);
106456
- const finalized = buildFinalizedReviewChildResult({
106457
- childLineItemId,
106458
- student: result.student,
106459
- parentAttemptId: result.sourcedId,
106460
- metadata: metadata2,
106461
- selection,
106462
- itemResult: {
106463
- itemIdentifier: outcome.itemIdentifier,
106464
- score: outcome.score,
106465
- isCorrect: outcome.isCorrect
106466
- },
106467
- submissionId,
106468
- timestamp: result.scoreDate
106469
- });
106470
- await this.requireClient().api.oneroster.assessmentResults.upsert(childResultId, finalized.resultUpdate);
106471
- } catch (error88) {
106472
- addEvent("assessment.review_child_terminal_restore_failed", {
106473
- "app.assessment.attempt_id": result.sourcedId,
106474
- "app.assessment.qti_item_identifier": selection.itemIdentifier,
106475
- "exception.type": errorType(error88),
106476
- "app.error.message": errorMessage(error88)
106477
- });
106478
- }
106479
- });
106480
- }
106481
- reviewItemOutcomes(input) {
106482
- const resultByItem = new Map(input.itemResults.map((itemResult) => [itemResult.itemIdentifier, itemResult]));
106483
- const administeredItems = administeredItemIdentifiers(input.metadata.itemSubmissions);
106484
- return input.metadata.review.selections.filter((selection) => administeredItems.has(selection.itemIdentifier)).map((selection) => {
106485
- const itemResult = resultByItem.get(selection.itemIdentifier);
106486
- const submission = itemAdministration(input.metadata.itemSubmissions, selection.itemIdentifier);
106487
- if (!itemResult || !submission) {
106488
- throw new Error(`Scoring omitted selected review item ${selection.itemIdentifier}`);
106489
- }
106490
- return {
106491
- itemIdentifier: selection.itemIdentifier,
106492
- standard: selection.standard,
106493
- answered: submission.answered,
106494
- score: itemResult.score,
106495
- isCorrect: itemResult.isCorrect
106496
- };
106497
- });
106498
- }
106499
105704
  async resolveAttemptAuthorization({ gameId, studentId, attemptId, user }, options = {}) {
106500
105705
  if (!options.developerAccessValidated) {
106501
105706
  await this.deps.validateDeveloperAccess(user, gameId);
@@ -106524,14 +105729,6 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
106524
105729
  const enrollment = enrollments.find((candidate) => candidate.id === metadata2.enrollmentId && candidate.course.id === integration.courseId);
106525
105730
  return { attempt: { result, metadata: metadata2, integration }, enrollment };
106526
105731
  }
106527
- async authorizeAttempt(params, options = {}) {
106528
- const authorization = await this.resolveAttemptAuthorization(params, options);
106529
- return this.requireActiveAttempt(authorization, params.attemptId);
106530
- }
106531
- requireActiveAttempt(authorization, attemptId) {
106532
- const enrollment = requireAssessmentMutationAccess(attemptId, authorization.attempt.integration.status, authorization.enrollment);
106533
- return { ...authorization.attempt, enrollment };
106534
- }
106535
105732
  async authorizeAttemptRead(params) {
106536
105733
  const { attempt, enrollment } = await this.resolveAttemptAuthorization(params);
106537
105734
  if ((!enrollment || attempt.integration.status === "deactivated") && !this.isSettled(attempt.result)) {
@@ -106542,7 +105739,7 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
106542
105739
  unauthorizedAttempt(attemptId) {
106543
105740
  return AssessmentRuntimeError.from(assessmentAttemptUnauthorized(attemptId));
106544
105741
  }
106545
- async snapshotForResult(result, metadata2, integration) {
105742
+ async snapshotForResult(result, metadata2, integration, suppliedTranscript) {
106546
105743
  if (isAssessmentAttemptSuperseded({
106547
105744
  inProgress: result.inProgress ?? "",
106548
105745
  scoreStatus: result.scoreStatus
@@ -106578,38 +105775,59 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
106578
105775
  result: buildCompletedAssessmentSubmitResult({ result, metadata: metadata2 })
106579
105776
  };
106580
105777
  }
106581
- const assessment = await this.loadAttemptAssessment(metadata2);
106582
- if (this.isPlatformRoutedDiagnosticMetadata(metadata2)) {
106583
- const routing = await this.loadAttemptDiagnosticRouting(metadata2, assessment);
106584
- await this.restoreDiagnosticChildResults({
106585
- result,
106586
- metadata: metadata2,
106587
- integration,
106588
- assessment
106589
- });
106590
- return this.diagnosticSnapshot(result, metadata2, assessment, integration, routing.state);
106591
- }
106592
- return this.snapshot(result, metadata2, assessment, integration);
106593
- }
106594
- diagnosticSnapshot(result, metadata2, assessment, integration, state) {
106595
- return buildDiagnosticAssessmentSnapshot({
106596
- attemptId: result.sourcedId,
106597
- metadata: metadata2,
106598
- assessment,
106599
- state,
106600
- activityData: this.activityData(metadata2, assessment, integration)
105778
+ const context2 = this.tokenContext({ result, metadata: metadata2, integration });
105779
+ const graded = await resumeAssessmentTranscript({
105780
+ suppliedTranscript,
105781
+ checkpointTranscript: metadata2.transcript,
105782
+ recover: async () => recoverAssessmentTranscript(context2, await this.studentAssessmentResults(context2.studentId)),
105783
+ grade: (transcript) => this.gradeTranscript(context2, transcript)
106601
105784
  });
105785
+ const session2 = await this.assessmentSession(context2, graded.transcript);
105786
+ const common2 = {
105787
+ attemptId: result.sourcedId,
105788
+ assessment: graded.assessment,
105789
+ activityData: this.activityData(metadata2, graded.assessment, integration)
105790
+ };
105791
+ return this.isPlatformRoutedDiagnosticMetadata(graded.metadata) ? {
105792
+ ...buildDiagnosticAssessmentSnapshot({
105793
+ ...common2,
105794
+ metadata: graded.metadata,
105795
+ state: graded.metadata.diagnostic.state
105796
+ }),
105797
+ ...session2
105798
+ } : {
105799
+ ...buildConventionalAssessmentSnapshot({ ...common2, metadata: graded.metadata }),
105800
+ ...session2
105801
+ };
105802
+ }
105803
+ async assessmentSession(context2, transcript = emptyAssessmentTranscript(context2.attemptId, context2.metadata.selectedTest.contentRevision)) {
105804
+ return { transcript, assessmentToken: await this.deps.mintAssessmentToken(context2) };
105805
+ }
105806
+ async diagnosticSnapshot(result, metadata2, assessment, integration, options) {
105807
+ return {
105808
+ ...buildDiagnosticAssessmentSnapshot({
105809
+ attemptId: result.sourcedId,
105810
+ metadata: metadata2,
105811
+ assessment,
105812
+ state: options.state,
105813
+ activityData: this.activityData(metadata2, assessment, integration)
105814
+ }),
105815
+ ...await this.assessmentSession(this.tokenContext({ result, metadata: metadata2, integration }))
105816
+ };
106602
105817
  }
106603
105818
  diagnosticRoutingSnapshot(metadata2, state) {
106604
105819
  return projectDiagnosticRoutingSnapshot(metadata2.diagnostic.routingRevision, state);
106605
105820
  }
106606
- snapshot(result, metadata2, assessment, integration) {
106607
- return buildConventionalAssessmentSnapshot({
106608
- attemptId: result.sourcedId,
106609
- metadata: metadata2,
106610
- assessment,
106611
- activityData: this.activityData(metadata2, assessment, integration)
106612
- });
105821
+ async snapshot(result, metadata2, assessment, integration) {
105822
+ return {
105823
+ ...buildConventionalAssessmentSnapshot({
105824
+ attemptId: result.sourcedId,
105825
+ metadata: metadata2,
105826
+ assessment,
105827
+ activityData: this.activityData(metadata2, assessment, integration)
105828
+ }),
105829
+ ...await this.assessmentSession(this.tokenContext({ result, metadata: metadata2, integration }))
105830
+ };
106613
105831
  }
106614
105832
  activityData(metadata2, assessment, integration) {
106615
105833
  const course = this.trackableCourse(integration, "assessment.activity_tracking_invalid_course_metadata");
@@ -106670,16 +105888,6 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
106670
105888
  isSettled(result) {
106671
105889
  return this.isAwaitingAward(result) || this.isCompleted(result);
106672
105890
  }
106673
- assertInProgress(result) {
106674
- if (!this.isOpen(result)) {
106675
- throw AssessmentRuntimeError.from(assessmentAttemptNotOpen(result.sourcedId));
106676
- }
106677
- }
106678
- assertResponseVersion(metadata2, expectedResponseVersion) {
106679
- if (metadata2.responseVersion !== expectedResponseVersion) {
106680
- throw AssessmentRuntimeError.from(assessmentResponseVersionConflict(expectedResponseVersion, metadata2.responseVersion));
106681
- }
106682
- }
106683
105891
  scoreFromResult(result, possible, metadata2) {
106684
105892
  return metadata2.score ?? assessmentScoreFromPercentage(result.score, possible);
106685
105893
  }
@@ -106747,7 +105955,9 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
106747
105955
  grade: integration.grade
106748
105956
  })
106749
105957
  });
106750
- return this.isPlatformRoutedDiagnosticMetadata(metadata2) ? this.diagnosticSnapshot(result, metadata2, assessment, integration, metadata2.diagnostic.state) : this.snapshot(result, metadata2, assessment, integration);
105958
+ return this.isPlatformRoutedDiagnosticMetadata(metadata2) ? this.diagnosticSnapshot(result, metadata2, assessment, integration, {
105959
+ state: metadata2.diagnostic.state
105960
+ }) : this.snapshot(result, metadata2, assessment, integration);
106751
105961
  }
106752
105962
  async claimAttemptId(scope, from) {
106753
105963
  for (let attemptNumber = from;attemptNumber < from + 20; attemptNumber += 1) {
@@ -106792,18 +106002,6 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
106792
106002
  game: game2
106793
106003
  };
106794
106004
  }
106795
- async putResultMetadata(result, metadata2) {
106796
- await this.requireClient().api.oneroster.assessmentResults.upsert(result.sourcedId, {
106797
- status: ONEROSTER_STATUS2.active,
106798
- assessmentLineItem: result.assessmentLineItem,
106799
- student: result.student,
106800
- score: result.score ?? 0,
106801
- scoreDate: result.scoreDate,
106802
- scoreStatus: result.scoreStatus,
106803
- ...result.inProgress === undefined || result.inProgress === null ? {} : { inProgress: result.inProgress },
106804
- metadata: mergeAssessmentRuntimeMetadata(result.metadata, metadata2)
106805
- });
106806
- }
106807
106005
  async emitCompletionBestEffort(input) {
106808
106006
  const { attempt } = input;
106809
106007
  const integration = attempt.integration;
@@ -110392,6 +109590,8 @@ function createPlatformServices(deps) {
110392
109590
  mintPlatformServiceToken,
110393
109591
  mintAssessmentPreparationReceipt,
110394
109592
  verifyAssessmentPreparationReceipt,
109593
+ mintAssessmentToken,
109594
+ verifyAssessmentToken,
110395
109595
  validateDeveloperAccessBySlug,
110396
109596
  validateDeveloperAccess,
110397
109597
  validateGameManagementAccess
@@ -110466,7 +109666,9 @@ function createPlatformServices(deps) {
110466
109666
  validateDeveloperAccess,
110467
109667
  assessmentRuntimeLock,
110468
109668
  mintAssessmentPreparationReceipt,
110469
- verifyAssessmentPreparationReceipt
109669
+ verifyAssessmentPreparationReceipt,
109670
+ mintAssessmentToken,
109671
+ verifyAssessmentToken
110470
109672
  });
110471
109673
  return {
110472
109674
  assessmentAssets,
@@ -111216,6 +110418,8 @@ function createServices(ctx) {
111216
110418
  mintPlatformServiceToken: auth2.mintPlatformServiceToken.bind(auth2),
111217
110419
  mintAssessmentPreparationReceipt: auth2.mintAssessmentPreparationReceipt.bind(auth2),
111218
110420
  verifyAssessmentPreparationReceipt: auth2.verifyAssessmentPreparationReceipt.bind(auth2),
110421
+ mintAssessmentToken: auth2.mintAssessmentToken.bind(auth2),
110422
+ verifyAssessmentToken: auth2.verifyAssessmentToken.bind(auth2),
111219
110423
  ...gaming.validators
111220
110424
  });
111221
110425
  const standalone = createStandaloneServices({ db: db2, auth: auth2, timeback: timeback2 });
@@ -111744,6 +110948,15 @@ function createSandboxAuthProvider() {
111744
110948
  });
111745
110949
  return { receipt, expiresAt: new Date(expiresAt).toISOString() };
111746
110950
  },
110951
+ async mintAssessmentToken(context2) {
110952
+ const token = `sandbox_assessment_${crypto.randomUUID()}`;
110953
+ sandboxAssessmentTokens.set(token, structuredClone(context2));
110954
+ return token;
110955
+ },
110956
+ async verifyAssessmentToken(token) {
110957
+ const context2 = sandboxAssessmentTokens.get(token);
110958
+ return context2 ? structuredClone(context2) : null;
110959
+ },
111747
110960
  async verifyAssessmentPreparationReceipt(receipt) {
111748
110961
  const prepared = sandboxAssessmentPreparationReceipts.get(receipt);
111749
110962
  if (!prepared) {
@@ -111796,13 +111009,18 @@ function createSandboxAuthProvider() {
111796
111009
  }
111797
111010
  };
111798
111011
  }
111799
- var sandboxApiKeys, sandboxAssessmentPreparationReceipts, ASSESSMENT_PREPARATION_RECEIPT_LIFETIME_MS;
111800
- var init_auth_provider = __esm(() => {
111012
+ var sandboxApiKeys, sandboxAssessmentPreparationReceipts, ASSESSMENT_PREPARATION_RECEIPT_LIFETIME_MS, sandboxAssessmentTokens;
111013
+ var init_auth_provider = __esm(async () => {
111801
111014
  init_src6();
111802
111015
  init_constants();
111016
+ await init_dist5();
111803
111017
  sandboxApiKeys = new Map;
111804
111018
  sandboxAssessmentPreparationReceipts = new Map;
111805
111019
  ASSESSMENT_PREPARATION_RECEIPT_LIFETIME_MS = 5 * 60 * 1000;
111020
+ sandboxAssessmentTokens = new TimebackCache({
111021
+ defaultTTL: 2 * 60 * 60 * 1000,
111022
+ maxSize: 1000
111023
+ });
111806
111024
  });
111807
111025
 
111808
111026
  // src/infrastructure/api/providers/cache.provider.ts
@@ -111963,10 +111181,10 @@ var init_storage_provider = __esm(() => {
111963
111181
  });
111964
111182
 
111965
111183
  // src/infrastructure/api/providers/index.ts
111966
- var init_providers = __esm(() => {
111967
- init_auth_provider();
111184
+ var init_providers = __esm(async () => {
111968
111185
  init_cache_provider();
111969
111186
  init_storage_provider();
111187
+ await init_auth_provider();
111970
111188
  });
111971
111189
 
111972
111190
  // src/infrastructure/api/context.ts
@@ -112036,10 +111254,10 @@ var init_context = __esm(async () => {
112036
111254
  init_config2();
112037
111255
  init_utils7();
112038
111256
  init_src6();
112039
- init_providers();
112040
111257
  await __promiseAll([
112041
111258
  init_factory(),
112042
- init_clients()
111259
+ init_clients(),
111260
+ init_providers()
112043
111261
  ]);
112044
111262
  });
112045
111263
 
@@ -168435,7 +167653,7 @@ function parseQtiLibraryParams(searchParams) {
168435
167653
  limit
168436
167654
  };
168437
167655
  }
168438
- var populateStudent, getUser, getUserEnrollments, getUserById, setupIntegration, getIntegrations, getRemovedIntegrations, createIntegration, updateIntegration, deactivateCourse, reactivateCourse, getIntegrationConfig, verifyIntegration, getConfig, deleteIntegrations, endActivity, heartbeat, advanceCourse, unenrollCourse, prepareRuntimeAssessment, startRuntimeAssessment, getRuntimeAssessment, getLatestRuntimeAssessment, saveRuntimeAssessment, submitRuntimeAssessmentItem, submitRuntimeAssessment, finalizeRuntimeAssessment, exportRuntimeAssessmentFixture, getStudentXp, getStudentMastery, getStudentHighestGradeMastered, getRoster, getStudentOverview, getGameMetrics, getStudentActivity, getGradeLevelTestResults, getGradeLevelTestReview, getActivityDetail, listMetricDiscrepancies, verifyMetricDiscrepancy, grantXp, adjustTime, adjustMastery, reconcileMasteryForConfigChange, searchStudents, enrollStudent, unenrollStudent, reactivateEnrollment, listAssessments, createAssessment, attachExistingAssessments, updateAssessment, synchronizeReviewBank, reorderQuestions, removeAssessment, listQuestions, listQuestionLibrary, listTestLibrary, copyAssessment, createQuestion, updateQuestion, removeQuestion, timeback2;
167656
+ var populateStudent, getUser, getUserEnrollments, getUserById, setupIntegration, getIntegrations, getRemovedIntegrations, createIntegration, updateIntegration, deactivateCourse, reactivateCourse, getIntegrationConfig, verifyIntegration, getConfig, deleteIntegrations, endActivity, heartbeat, advanceCourse, unenrollCourse, prepareRuntimeAssessment, startRuntimeAssessment, getRuntimeAssessment, getLatestRuntimeAssessment, resumeRuntimeAssessment, saveRuntimeAssessment, recordRuntimeAssessmentProgress, submitRuntimeAssessmentItem, submitRuntimeAssessment, finalizeRuntimeAssessment, exportRuntimeAssessmentFixture, getStudentXp, getStudentMastery, getStudentHighestGradeMastered, getRoster, getStudentOverview, getGameMetrics, getStudentActivity, getGradeLevelTestResults, getGradeLevelTestReview, getActivityDetail, listMetricDiscrepancies, verifyMetricDiscrepancy, grantXp, adjustTime, adjustMastery, reconcileMasteryForConfigChange, searchStudents, enrollStudent, unenrollStudent, reactivateEnrollment, listAssessments, createAssessment, attachExistingAssessments, updateAssessment, synchronizeReviewBank, reorderQuestions, removeAssessment, listQuestions, listQuestionLibrary, listTestLibrary, copyAssessment, createQuestion, updateQuestion, removeQuestion, timeback2;
168439
167657
  var init_timeback_controller = __esm(() => {
168440
167658
  init_esm();
168441
167659
  init_src();
@@ -168737,6 +167955,20 @@ var init_timeback_controller = __esm(() => {
168737
167955
  user: ctx.user
168738
167956
  });
168739
167957
  });
167958
+ resumeRuntimeAssessment = requireDeveloper(async (ctx) => {
167959
+ const attemptId = ctx.params.attemptId;
167960
+ const body2 = await parseRequestBody(ctx.request, ResumeRuntimeAssessmentRequestSchema);
167961
+ if (!attemptId) {
167962
+ throw ApiError.badRequest("Missing attemptId");
167963
+ }
167964
+ return ctx.services.timebackAssessmentRuntime.resume({
167965
+ gameId: body2.gameId,
167966
+ studentId: body2.studentId,
167967
+ attemptId,
167968
+ input: { transcript: body2.transcript },
167969
+ user: ctx.user
167970
+ });
167971
+ });
168740
167972
  saveRuntimeAssessment = requireDeveloper(async (ctx) => {
168741
167973
  const attemptId = ctx.params.attemptId;
168742
167974
  const body2 = await parseRequestBody(ctx.request, SaveRuntimeAssessmentRequestSchema);
@@ -168749,7 +167981,26 @@ var init_timeback_controller = __esm(() => {
168749
167981
  attemptId,
168750
167982
  input: {
168751
167983
  expectedResponseVersion: body2.expectedResponseVersion,
168752
- responses: body2.responses
167984
+ transcript: body2.transcript
167985
+ },
167986
+ user: ctx.user
167987
+ });
167988
+ });
167989
+ recordRuntimeAssessmentProgress = requireDeveloper(async (ctx) => {
167990
+ const attemptId = ctx.params.attemptId;
167991
+ const body2 = await parseRequestBody(ctx.request, RecordRuntimeAssessmentProgressRequestSchema);
167992
+ if (!attemptId) {
167993
+ throw ApiError.badRequest("Missing attemptId");
167994
+ }
167995
+ return ctx.services.timebackAssessmentRuntime.recordProgress({
167996
+ gameId: body2.gameId,
167997
+ studentId: body2.studentId,
167998
+ attemptId,
167999
+ input: {
168000
+ assessmentToken: body2.assessmentToken,
168001
+ transcript: body2.transcript,
168002
+ expectedResponseVersion: body2.expectedResponseVersion,
168003
+ itemIdentifier: body2.itemIdentifier
168753
168004
  },
168754
168005
  user: ctx.user
168755
168006
  });
@@ -168765,6 +168016,8 @@ var init_timeback_controller = __esm(() => {
168765
168016
  studentId: body2.studentId,
168766
168017
  attemptId,
168767
168018
  input: {
168019
+ assessmentToken: body2.assessmentToken,
168020
+ transcript: body2.transcript,
168768
168021
  expectedResponseVersion: body2.expectedResponseVersion,
168769
168022
  submissionId: body2.submissionId,
168770
168023
  itemIdentifier: body2.itemIdentifier,
@@ -168787,6 +168040,7 @@ var init_timeback_controller = __esm(() => {
168787
168040
  input: {
168788
168041
  expectedResponseVersion: body2.expectedResponseVersion,
168789
168042
  submissionId: body2.submissionId,
168043
+ transcript: body2.transcript,
168790
168044
  ...body2.session ? { session: body2.session } : {}
168791
168045
  },
168792
168046
  game: {
@@ -169264,6 +168518,8 @@ var init_timeback_controller = __esm(() => {
169264
168518
  getLatestRuntimeAssessment,
169265
168519
  getRuntimeAssessment,
169266
168520
  saveRuntimeAssessment,
168521
+ resumeRuntimeAssessment,
168522
+ recordRuntimeAssessmentProgress,
169267
168523
  submitRuntimeAssessmentItem,
169268
168524
  submitRuntimeAssessment,
169269
168525
  finalizeRuntimeAssessment,
@@ -170256,6 +169512,8 @@ var init_timeback8 = __esm(async () => {
170256
169512
  timebackRouter.post("/assessments/start", handle2(timeback2.startRuntimeAssessment));
170257
169513
  timebackRouter.get("/assessments/latest", handle2(timeback2.getLatestRuntimeAssessment));
170258
169514
  timebackRouter.get("/assessments/:attemptId", handle2(timeback2.getRuntimeAssessment));
169515
+ timebackRouter.post("/assessments/:attemptId/resume", handle2(timeback2.resumeRuntimeAssessment));
169516
+ timebackRouter.post("/assessments/:attemptId/progress", handle2(timeback2.recordRuntimeAssessmentProgress));
170259
169517
  timebackRouter.post("/assessments/:attemptId/save", handle2(timeback2.saveRuntimeAssessment));
170260
169518
  timebackRouter.post("/assessments/:attemptId/submit-item", handle2(timeback2.submitRuntimeAssessmentItem));
170261
169519
  timebackRouter.post("/assessments/:attemptId/submit", handle2(timeback2.submitRuntimeAssessment));
@@ -170664,13 +169922,13 @@ var init_server = __esm(async () => {
170664
169922
  init_port();
170665
169923
  init_package();
170666
169924
  init_config();
170667
- init_providers();
170668
169925
  init_app();
170669
169926
  init_database2();
170670
169927
  init_options();
170671
169928
  await __promiseAll([
170672
169929
  init_api3(),
170673
- init_context()
169930
+ init_context(),
169931
+ init_providers()
170674
169932
  ]);
170675
169933
  version5 = package_default.version;
170676
169934
  });