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