@playcademy/vite-plugin 1.2.1-beta.17 → 1.2.1-beta.19

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.
Files changed (2) hide show
  1. package/dist/index.js +100 -70
  2. package/package.json +4 -4
package/dist/index.js CHANGED
@@ -24315,7 +24315,7 @@ import path2 from "node:path";
24315
24315
  // package.json
24316
24316
  var package_default = {
24317
24317
  name: "@playcademy/vite-plugin",
24318
- version: "1.2.1-beta.17",
24318
+ version: "1.2.1-beta.19",
24319
24319
  type: "module",
24320
24320
  exports: {
24321
24321
  ".": {
@@ -25076,7 +25076,7 @@ var init_timeback2 = __esm(() => {
25076
25076
  ASSESSMENT_PURPOSES = ["end_of_course", "diagnostic", "review", "mastery"];
25077
25077
  TIMEBACK_ASSESSMENT_REVIEW_REQUEST_LIMITS = {
25078
25078
  standards: 20,
25079
- itemsPerStandard: 5,
25079
+ candidateItemsPerStandard: 2,
25080
25080
  standardFieldLength: 128
25081
25081
  };
25082
25082
  TIMEBACK_COURSE_DEFAULTS = {
@@ -25921,7 +25921,7 @@ var package_default2;
25921
25921
  var init_package = __esm(() => {
25922
25922
  package_default2 = {
25923
25923
  name: "@playcademy/sandbox",
25924
- version: "0.7.1-beta.16",
25924
+ version: "0.7.1-beta.18",
25925
25925
  description: "Local development server for Playcademy game development",
25926
25926
  type: "module",
25927
25927
  exports: {
@@ -34412,7 +34412,7 @@ function assessmentReviewBankShortage(fulfillment) {
34412
34412
  };
34413
34413
  }
34414
34414
  function assessmentFlowForPurpose(purpose) {
34415
- return purpose === "review" || purpose === "mastery" ? "item-submit" : "attempt-submit";
34415
+ return purpose === "review" ? "item-submit" : "attempt-submit";
34416
34416
  }
34417
34417
  function isObject(value) {
34418
34418
  return typeof value === "object" && value !== null;
@@ -34614,6 +34614,12 @@ function interactionResponseValidationMessage(itemIdentifier, interaction, respo
34614
34614
  function invalidResponse(message) {
34615
34615
  return { code: "INVALID_RESPONSE", message };
34616
34616
  }
34617
+ function administeredItemIdentifiers(itemSubmissions) {
34618
+ return new Set(itemSubmissions.map((submission) => submission.itemIdentifier));
34619
+ }
34620
+ function itemAdministration(itemSubmissions, itemIdentifier) {
34621
+ return itemSubmissions.find((submission) => submission.itemIdentifier === itemIdentifier);
34622
+ }
34617
34623
  function responseValuesEqual(left, right) {
34618
34624
  if (left === undefined || right === undefined) {
34619
34625
  return left === right;
@@ -34664,15 +34670,15 @@ function prepareAssessmentItemSubmission(state, input) {
34664
34670
  failure: assessmentResponseVersionConflict(input.expectedResponseVersion, state.responseVersion)
34665
34671
  };
34666
34672
  }
34667
- const committed = new Set(state.itemSubmissions.map((submission) => submission.itemIdentifier));
34668
- const itemIndex = state.assessment.items.findIndex((item) => !committed.has(item.identifier));
34669
- const nextItem = state.assessment.items[itemIndex];
34670
- if (nextItem?.identifier !== input.itemIdentifier) {
34673
+ const itemIndex = state.assessment.items.findIndex((item) => item.identifier === input.itemIdentifier);
34674
+ const previousItemIdentifier = state.itemSubmissions.at(-1)?.itemIdentifier;
34675
+ const previousItemIndex = state.assessment.items.findIndex((item) => item.identifier === previousItemIdentifier);
34676
+ if (itemIndex === -1 || itemIndex <= previousItemIndex) {
34671
34677
  return {
34672
34678
  action: "reject",
34673
- failure: assessmentFlowViolation("Items must be submitted once and in presentation order.", {
34679
+ failure: assessmentFlowViolation("Items must be submitted once and in candidate order.", {
34674
34680
  itemIdentifier: input.itemIdentifier,
34675
- expectedItemIdentifier: nextItem?.identifier ?? null,
34681
+ previousItemIdentifier: previousItemIdentifier ?? null,
34676
34682
  flow
34677
34683
  })
34678
34684
  };
@@ -34695,10 +34701,11 @@ function prepareAssessmentItemSubmission(state, input) {
34695
34701
  answered: Object.keys(responses[input.itemIdentifier] ?? {}).length > 0
34696
34702
  };
34697
34703
  }
34698
- function completeAssessmentItemSubmission(state, input, prepared, scoring) {
34704
+ function completeAssessmentItemSubmission(state, input, prepared, scoring, submittedAt) {
34699
34705
  const submission = {
34700
34706
  submissionId: input.submissionId,
34701
34707
  itemIdentifier: input.itemIdentifier,
34708
+ submittedAt,
34702
34709
  responseVersion: prepared.responseVersion,
34703
34710
  answered: prepared.answered,
34704
34711
  score: scoring.score,
@@ -34712,8 +34719,8 @@ function completeAssessmentItemSubmission(state, input, prepared, scoring) {
34712
34719
  };
34713
34720
  }
34714
34721
  function assessmentResponseUpdateFlowFailure(purpose, itemSubmissions, update) {
34715
- const committed = new Set(itemSubmissions.map((submission) => submission.itemIdentifier));
34716
- const committedUpdate = Object.keys(update).find((identifier) => committed.has(identifier));
34722
+ const administered = administeredItemIdentifiers(itemSubmissions);
34723
+ const committedUpdate = Object.keys(update).find((identifier) => administered.has(identifier));
34717
34724
  return committedUpdate ? assessmentFlowViolation("A submitted item response cannot be edited.", {
34718
34725
  itemIdentifier: committedUpdate,
34719
34726
  flow: assessmentFlowForPurpose(purpose)
@@ -34721,11 +34728,17 @@ function assessmentResponseUpdateFlowFailure(purpose, itemSubmissions, update) {
34721
34728
  }
34722
34729
  function assessmentCompletionFlowFailure(purpose, itemSubmissions, itemCount) {
34723
34730
  const flow = assessmentFlowForPurpose(purpose);
34724
- return flow === "item-submit" && itemSubmissions.length !== itemCount ? assessmentFlowViolation("Every item must be submitted before the attempt can be completed.", {
34731
+ if (flow !== "item-submit") {
34732
+ return null;
34733
+ }
34734
+ if (itemSubmissions.length > 0 && itemSubmissions.length <= itemCount) {
34735
+ return null;
34736
+ }
34737
+ return assessmentFlowViolation("At least one candidate must be administered before the review can be completed.", {
34725
34738
  flow,
34726
34739
  submittedItemCount: itemSubmissions.length,
34727
34740
  itemCount
34728
- }) : null;
34741
+ });
34729
34742
  }
34730
34743
  function selectAssessmentCourseCandidate(candidates, subjectSpecified) {
34731
34744
  if (candidates.length === 0) {
@@ -34901,15 +34914,15 @@ function canonicalRequestStandards(standards) {
34901
34914
  return [...canonical.values()].toSorted((left, right) => compareCodeUnits(assessmentStandardRefKey(left), assessmentStandardRefKey(right)));
34902
34915
  }
34903
34916
  function normalizeReviewRequest(request) {
34904
- const itemsPerStandard = request.itemsPerStandard ?? 1;
34905
- if (!Number.isInteger(itemsPerStandard) || itemsPerStandard <= 0 || itemsPerStandard > TIMEBACK_ASSESSMENT_REVIEW_REQUEST_LIMITS.itemsPerStandard) {
34906
- throw new RangeError(`itemsPerStandard must be a positive integer at most ${TIMEBACK_ASSESSMENT_REVIEW_REQUEST_LIMITS.itemsPerStandard}`);
34917
+ const candidateItemsPerStandard = request.candidateItemsPerStandard ?? 1;
34918
+ if (!Number.isInteger(candidateItemsPerStandard) || candidateItemsPerStandard <= 0 || candidateItemsPerStandard > TIMEBACK_ASSESSMENT_REVIEW_REQUEST_LIMITS.candidateItemsPerStandard) {
34919
+ throw new RangeError(`candidateItemsPerStandard must be a positive integer at most ${TIMEBACK_ASSESSMENT_REVIEW_REQUEST_LIMITS.candidateItemsPerStandard}`);
34907
34920
  }
34908
34921
  const standards = canonicalRequestStandards(request.standards);
34909
34922
  if (standards.length === 0) {
34910
34923
  throw new Error("At least one review standard is required");
34911
34924
  }
34912
- return { standards, itemsPerStandard };
34925
+ return { standards, candidateItemsPerStandard };
34913
34926
  }
34914
34927
  function reviewRequestFingerprint(request, policy) {
34915
34928
  const normalized = normalizeReviewRequest(request);
@@ -34918,7 +34931,7 @@ function reviewRequestFingerprint(request, policy) {
34918
34931
  policy: {
34919
34932
  version: policy.version
34920
34933
  },
34921
- itemsPerStandard: normalized.itemsPerStandard,
34934
+ candidateItemsPerStandard: normalized.candidateItemsPerStandard,
34922
34935
  standardKeys: normalized.standards.map(assessmentStandardRefKey)
34923
34936
  });
34924
34937
  }
@@ -35018,7 +35031,7 @@ function rankedCandidatesByStandard(bank, exposures) {
35018
35031
  return byStandard;
35019
35032
  }
35020
35033
  function reviewSlots(request) {
35021
- return request.standards.flatMap((standard) => Array.from({ length: request.itemsPerStandard }, () => ({
35034
+ return request.standards.flatMap((standard) => Array.from({ length: request.candidateItemsPerStandard }, () => ({
35022
35035
  standard,
35023
35036
  standardKey: assessmentStandardRefKey(standard)
35024
35037
  })));
@@ -35122,7 +35135,7 @@ function preferredDistinctMatching(slots, candidatesByStandard) {
35122
35135
  return new Map(slotItemEdges.filter((candidate) => candidate.edge.capacity === 0).map((candidate) => [candidate.slotIndex, candidate.itemIdentifier]));
35123
35136
  }
35124
35137
  function fulfillment(request, candidatesByStandard, selections) {
35125
- const requestedItemCount = request.standards.length * request.itemsPerStandard;
35138
+ const requestedItemCount = request.standards.length * request.candidateItemsPerStandard;
35126
35139
  const selectedByStandard = new Map;
35127
35140
  for (const selection of selections) {
35128
35141
  const key = assessmentStandardRefKey(selection.standard);
@@ -35131,10 +35144,10 @@ function fulfillment(request, candidatesByStandard, selections) {
35131
35144
  const shortages = request.standards.flatMap((standard) => {
35132
35145
  const key = assessmentStandardRefKey(standard);
35133
35146
  const selected = selectedByStandard.get(key) ?? 0;
35134
- return selected < request.itemsPerStandard ? [
35147
+ return selected < request.candidateItemsPerStandard ? [
35135
35148
  {
35136
35149
  standard,
35137
- requested: request.itemsPerStandard,
35150
+ requested: request.candidateItemsPerStandard,
35138
35151
  selected,
35139
35152
  eligible: candidatesByStandard.get(key)?.length ?? 0
35140
35153
  }
@@ -35176,9 +35189,9 @@ function projectReviewAssessment(assessment, bank, selections) {
35176
35189
  throw new Error(`Review bank ${bank.bankRevision} was built from content revision ${bank.sourceContentRevision}, not ${assessment.contentRevision}`);
35177
35190
  }
35178
35191
  const selectedIdentifiers = new Set(selections.map((selection) => selection.itemIdentifier));
35179
- const knownIdentifiers = new Set(assessment.items.map((item) => item.identifier));
35192
+ const itemByIdentifier = new Map(assessment.items.map((item) => [item.identifier, item]));
35180
35193
  const indexedItems = new Map(bank.items.map((item) => [item.itemIdentifier, item]));
35181
- const missingIdentifiers = [...selectedIdentifiers].filter((identifier) => !knownIdentifiers.has(identifier) || !indexedItems.has(identifier));
35194
+ const missingIdentifiers = [...selectedIdentifiers].filter((identifier) => !itemByIdentifier.has(identifier) || !indexedItems.has(identifier));
35182
35195
  if (missingIdentifiers.length > 0) {
35183
35196
  throw new Error(`Selected review items are absent from the pinned bank: ${missingIdentifiers.join(", ")}`);
35184
35197
  }
@@ -35194,7 +35207,7 @@ function projectReviewAssessment(assessment, bank, selections) {
35194
35207
  return structuredClone({
35195
35208
  ...assessment,
35196
35209
  contentRevision: bank.bankRevision,
35197
- items: assessment.items.filter((item) => selectedIdentifiers.has(item.identifier))
35210
+ items: selections.map((selection) => itemByIdentifier.get(selection.itemIdentifier))
35198
35211
  });
35199
35212
  }
35200
35213
  function assessmentItemCorrectness(verdicts) {
@@ -35253,10 +35266,10 @@ function isReviewItemOutcome(value) {
35253
35266
  return isRecord(value) && typeof value.itemIdentifier === "string" && value.itemIdentifier.length > 0 && isAssessmentStandardRef(value.standard) && typeof value.answered === "boolean" && isAssessmentScore(value.score) && (value.isCorrect === null || typeof value.isCorrect === "boolean");
35254
35267
  }
35255
35268
  function isAssessmentItemSubmission(value) {
35256
- return isRecord(value) && typeof value.submissionId === "string" && value.submissionId.length > 0 && typeof value.itemIdentifier === "string" && value.itemIdentifier.length > 0 && Number.isInteger(value.responseVersion) && typeof value.answered === "boolean" && isAssessmentScore(value.score) && (value.isCorrect === null || typeof value.isCorrect === "boolean");
35269
+ return isRecord(value) && typeof value.submissionId === "string" && value.submissionId.length > 0 && typeof value.itemIdentifier === "string" && value.itemIdentifier.length > 0 && typeof value.submittedAt === "string" && Number.isInteger(value.responseVersion) && typeof value.answered === "boolean" && isAssessmentScore(value.score) && (value.isCorrect === null || typeof value.isCorrect === "boolean");
35257
35270
  }
35258
35271
  function isReviewAttemptMetadata(value) {
35259
- return isRecord(value) && typeof value.requestFingerprint === "string" && typeof value.bankRevision === "string" && Array.isArray(value.standards) && value.standards.every(isAssessmentStandardRef) && Number.isInteger(value.itemsPerStandard) && Array.isArray(value.selections) && value.selections.every((selection) => isRecord(selection) && isAssessmentStandardRef(selection.standard) && typeof selection.itemIdentifier === "string") && isReviewFulfillment(value.fulfillment);
35272
+ return isRecord(value) && typeof value.requestFingerprint === "string" && typeof value.bankRevision === "string" && Array.isArray(value.standards) && value.standards.every(isAssessmentStandardRef) && Number.isInteger(value.candidateItemsPerStandard) && Array.isArray(value.selections) && value.selections.every((selection) => isRecord(selection) && isAssessmentStandardRef(selection.standard) && typeof selection.itemIdentifier === "string") && isReviewFulfillment(value.fulfillment);
35260
35273
  }
35261
35274
  function isMasteryAttemptMetadata(value) {
35262
35275
  return isRecord(value) && typeof value.requestFingerprint === "string" && isAssessmentStandardRef(value.standard);
@@ -35626,7 +35639,7 @@ var init_assessment_runtime = __esm(() => {
35626
35639
  StartAssessmentBaseSchema.extend({
35627
35640
  purpose: exports_external.literal("review"),
35628
35641
  standards: exports_external.array(AssessmentStandardRefSchema).min(1).max(TIMEBACK_ASSESSMENT_REVIEW_REQUEST_LIMITS.standards),
35629
- itemsPerStandard: exports_external.number().int().positive().max(TIMEBACK_ASSESSMENT_REVIEW_REQUEST_LIMITS.itemsPerStandard).optional()
35642
+ candidateItemsPerStandard: exports_external.number().int().positive().max(TIMEBACK_ASSESSMENT_REVIEW_REQUEST_LIMITS.candidateItemsPerStandard).optional()
35630
35643
  }),
35631
35644
  StartAssessmentBaseSchema.extend({
35632
35645
  purpose: exports_external.literal("mastery"),
@@ -116804,6 +116817,10 @@ var init_timeback4 = __esm(() => {
116804
116817
  init_assessment_runtime2();
116805
116818
  });
116806
116819
  function buildReviewItemResultMetadata(input) {
116820
+ const administration = itemAdministration(input.metadata.itemSubmissions, input.selection.itemIdentifier);
116821
+ if (!administration) {
116822
+ throw new Error(`Cannot create a review result for unadministered candidate ${input.selection.itemIdentifier}`);
116823
+ }
116807
116824
  return {
116808
116825
  version: PLAYCADEMY_ASSESSMENT_ITEM_RESULT_METADATA_VERSION,
116809
116826
  parentAttemptId: input.parentAttemptId,
@@ -116813,7 +116830,7 @@ function buildReviewItemResultMetadata(input) {
116813
116830
  bankRevision: input.metadata.review.bankRevision,
116814
116831
  itemIdentifier: input.selection.itemIdentifier,
116815
116832
  standard: input.selection.standard,
116816
- deliveredAt: input.metadata.startedAt,
116833
+ deliveredAt: administration.submittedAt,
116817
116834
  responseVersion: input.responseVersion ?? input.metadata.responseVersion,
116818
116835
  responses: input.metadata.responses[input.selection.itemIdentifier] ?? {},
116819
116836
  ...input.submissionId ? { submissionId: input.submissionId } : {},
@@ -116868,7 +116885,8 @@ function resolveReviewChildResult(input) {
116868
116885
  }
116869
116886
  const childMetadata = playcademyAssessmentItemResultMetadata(input.childResult.metadata);
116870
116887
  const expectedResponses = input.parentMetadata.responses[input.selection.itemIdentifier] ?? {};
116871
- 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 !== input.parentMetadata.startedAt || childMetadata.responseVersion > input.parentMetadata.responseVersion || canonicalJson(childMetadata.responses) !== canonicalJson(expectedResponses)) {
116888
+ const administration = itemAdministration(input.parentMetadata.itemSubmissions, input.selection.itemIdentifier);
116889
+ 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 || canonicalJson(childMetadata.responses) !== canonicalJson(expectedResponses)) {
116872
116890
  return { action: "restore", reason: "incompatible" };
116873
116891
  }
116874
116892
  return input.childResult.status === "active" ? { action: "reuse", metadata: childMetadata } : { action: "restore", reason: "inactive" };
@@ -117556,7 +117574,7 @@ class TimebackAssessmentRuntimeService {
117556
117574
  requestFingerprint,
117557
117575
  bankRevision: source.bank.bankRevision,
117558
117576
  standards: [...normalizedRequest.standards],
117559
- itemsPerStandard: normalizedRequest.itemsPerStandard,
117577
+ candidateItemsPerStandard: normalizedRequest.candidateItemsPerStandard,
117560
117578
  selections: [...selected.selections],
117561
117579
  fulfillment: selected.fulfillment
117562
117580
  }
@@ -117688,9 +117706,6 @@ class TimebackAssessmentRuntimeService {
117688
117706
  updatedAt: new Date().toISOString()
117689
117707
  };
117690
117708
  await this.putResultMetadata(attempt.result, metadata2);
117691
- if (metadata2.purpose === "review") {
117692
- await this.putReviewChildResponses(attempt.result, metadata2, new Set(Object.keys(input.responses)));
117693
- }
117694
117709
  return {
117695
117710
  attemptId: attempt.result.sourcedId,
117696
117711
  responseVersion: metadata2.responseVersion,
@@ -117758,13 +117773,14 @@ class TimebackAssessmentRuntimeService {
117758
117773
  if (!preview.scoring) {
117759
117774
  throw new Error("Prepared item submission is missing its score");
117760
117775
  }
117761
- const completed = completeAssessmentItemSubmission(attempt.metadata, input, transition, preview.scoring);
117776
+ const submittedAt = new Date().toISOString();
117777
+ const completed = completeAssessmentItemSubmission(attempt.metadata, input, transition, preview.scoring, submittedAt);
117762
117778
  const metadata2 = {
117763
117779
  ...attempt.metadata,
117764
117780
  responses: completed.responses,
117765
117781
  responseVersion: completed.responseVersion,
117766
117782
  itemSubmissions: completed.itemSubmissions,
117767
- updatedAt: new Date().toISOString()
117783
+ updatedAt: submittedAt
117768
117784
  };
117769
117785
  await this.putResultMetadata(attempt.result, metadata2);
117770
117786
  return {
@@ -117869,12 +117885,13 @@ class TimebackAssessmentRuntimeService {
117869
117885
  async scoreSubmission(attempt) {
117870
117886
  const assessment = await this.loadAttemptAssessment(attempt.metadata);
117871
117887
  validateAssessmentResponses(assessment, attempt.metadata.responses);
117872
- const itemResults = await this.scoreItems(assessment, attempt.metadata.responses);
117888
+ const administeredItems = attempt.metadata.purpose === "review" ? administeredItemIdentifiers(attempt.metadata.itemSubmissions) : undefined;
117889
+ const itemResults = await this.scoreItems(assessment, attempt.metadata.responses, administeredItems);
117873
117890
  return {
117874
117891
  responseVersion: attempt.metadata.responseVersion,
117875
117892
  assessment,
117876
117893
  itemResults,
117877
- score: aggregateAssessmentScore(itemResults.map((result) => result.score.earned), assessmentPossibleScore(assessment)),
117894
+ score: aggregateAssessmentScore(itemResults.map((result) => result.score.earned), itemResults.reduce((total, result) => total + result.score.possible, 0)),
117878
117895
  correctQuestions: countCorrectQuestions(itemResults.map((result) => result.isCorrect))
117879
117896
  };
117880
117897
  }
@@ -117967,7 +117984,7 @@ class TimebackAssessmentRuntimeService {
117967
117984
  submissionId: input.submissionId,
117968
117985
  timestamp: timestamp6,
117969
117986
  testName: assessment.title,
117970
- totalQuestions: assessment.items.length,
117987
+ totalQuestions: itemResults.length,
117971
117988
  correctQuestions,
117972
117989
  ...input.session ? { session: input.session } : {},
117973
117990
  ...reviewOutcomes ? { itemOutcomes: reviewOutcomes } : {}
@@ -117998,7 +118015,7 @@ class TimebackAssessmentRuntimeService {
117998
118015
  testIdentifier: assessment.identifier,
117999
118016
  testName: assessment.title,
118000
118017
  score,
118001
- totalQuestions: assessment.items.length,
118018
+ totalQuestions: itemResults.length,
118002
118019
  correctQuestions,
118003
118020
  submissionId: input.submissionId,
118004
118021
  gameId: params.gameId,
@@ -118542,11 +118559,21 @@ class TimebackAssessmentRuntimeService {
118542
118559
  if (physicalItems.size !== input.metadata.review.selections.length) {
118543
118560
  throw new Error("MVP review selection must use each physical item at most once");
118544
118561
  }
118562
+ const administeredItems = administeredItemIdentifiers(input.metadata.itemSubmissions);
118545
118563
  await runWithConcurrency(input.metadata.review.selections, TimebackAssessmentRuntimeService.SCORING_CONCURRENCY, async (selection) => {
118546
118564
  const item = itemByIdentifier.get(selection.itemIdentifier);
118547
118565
  if (!item) {
118548
118566
  throw new Error(`Review assessment is missing selected item ${selection.itemIdentifier}`);
118549
118567
  }
118568
+ if (!administeredItems.has(selection.itemIdentifier)) {
118569
+ await this.ensureReviewQuestionLineItem({
118570
+ parentLineItemId: input.result.assessmentLineItem.sourcedId,
118571
+ integration: input.integration,
118572
+ bank: input.source.bank,
118573
+ item
118574
+ });
118575
+ return;
118576
+ }
118550
118577
  const childLineItemId = await reviewQuestionLineItemId(input.result.assessmentLineItem.sourcedId, item.identifier);
118551
118578
  const childResultId = await reviewQuestionResultId(input.result.sourcedId, childLineItemId);
118552
118579
  const streamedChildResult = input.existingChildResults.get(childResultId);
@@ -118593,33 +118620,35 @@ class TimebackAssessmentRuntimeService {
118593
118620
  });
118594
118621
  });
118595
118622
  }
118596
- async putReviewChildResponses(result, metadata2, changedItemIdentifiers) {
118597
- await runWithConcurrency(metadata2.review.selections.filter((selection) => changedItemIdentifiers.has(selection.itemIdentifier)), TimebackAssessmentRuntimeService.SCORING_CONCURRENCY, async (selection) => {
118598
- try {
118599
- const childLineItemId = await reviewQuestionLineItemId(result.assessmentLineItem.sourcedId, selection.itemIdentifier);
118600
- const childResultId = await reviewQuestionResultId(result.sourcedId, childLineItemId);
118601
- const childMetadata = buildReviewItemResultMetadata({
118602
- metadata: metadata2,
118603
- selection,
118604
- parentAttemptId: result.sourcedId
118605
- });
118606
- await this.requireClient().api.oneroster.assessmentResults.upsert(childResultId, buildOpenReviewChildResult({
118607
- childLineItemId,
118608
- student: result.student,
118609
- metadata: childMetadata
118610
- }));
118611
- } catch (error88) {
118612
- addEvent("assessment.review_child_response_projection_failed", {
118613
- "app.assessment.attempt_id": result.sourcedId,
118614
- "app.assessment.qti_item_identifier": selection.itemIdentifier,
118615
- "exception.type": errorType(error88),
118616
- "app.error.message": errorMessage2(error88)
118617
- });
118618
- }
118619
- });
118623
+ async putReviewChildResponse(result, metadata2, itemIdentifier) {
118624
+ const selection = metadata2.review.selections.find((candidate) => candidate.itemIdentifier === itemIdentifier);
118625
+ if (!selection) {
118626
+ return;
118627
+ }
118628
+ try {
118629
+ const childLineItemId = await reviewQuestionLineItemId(result.assessmentLineItem.sourcedId, selection.itemIdentifier);
118630
+ const childResultId = await reviewQuestionResultId(result.sourcedId, childLineItemId);
118631
+ const childMetadata = buildReviewItemResultMetadata({
118632
+ metadata: metadata2,
118633
+ selection,
118634
+ parentAttemptId: result.sourcedId
118635
+ });
118636
+ await this.requireClient().api.oneroster.assessmentResults.upsert(childResultId, buildOpenReviewChildResult({
118637
+ childLineItemId,
118638
+ student: result.student,
118639
+ metadata: childMetadata
118640
+ }));
118641
+ } catch (error88) {
118642
+ addEvent("assessment.review_child_response_projection_failed", {
118643
+ "app.assessment.attempt_id": result.sourcedId,
118644
+ "app.assessment.qti_item_identifier": selection.itemIdentifier,
118645
+ "exception.type": errorType(error88),
118646
+ "app.error.message": errorMessage2(error88)
118647
+ });
118648
+ }
118620
118649
  }
118621
118650
  async projectReviewItemResponse(params, projection) {
118622
- await this.putReviewChildResponses(projection.result, projection.metadata, new Set([projection.itemIdentifier]));
118651
+ await this.putReviewChildResponse(projection.result, projection.metadata, projection.itemIdentifier);
118623
118652
  try {
118624
118653
  await this.crossAttemptLockBarrier(params.attemptId);
118625
118654
  const latest = await this.peekAttempt(params);
@@ -118698,7 +118727,8 @@ class TimebackAssessmentRuntimeService {
118698
118727
  }
118699
118728
  async finalizeReviewChildResults(input) {
118700
118729
  const resultByItem = new Map(input.itemResults.map((itemResult) => [itemResult.itemIdentifier, itemResult]));
118701
- return runWithConcurrency(input.metadata.review.selections, TimebackAssessmentRuntimeService.SCORING_CONCURRENCY, async (selection) => {
118730
+ const administeredItems = administeredItemIdentifiers(input.metadata.itemSubmissions);
118731
+ return runWithConcurrency(input.metadata.review.selections.filter((selection) => administeredItems.has(selection.itemIdentifier)), TimebackAssessmentRuntimeService.SCORING_CONCURRENCY, async (selection) => {
118702
118732
  const itemResult = resultByItem.get(selection.itemIdentifier);
118703
118733
  if (!itemResult) {
118704
118734
  throw new Error(`Scoring omitted selected review item ${selection.itemIdentifier}`);
@@ -118814,7 +118844,7 @@ class TimebackAssessmentRuntimeService {
118814
118844
  kind: "standards-review",
118815
118845
  purpose: metadata2.purpose,
118816
118846
  standards: [...metadata2.review.standards],
118817
- itemsPerStandard: metadata2.review.itemsPerStandard,
118847
+ candidateItemsPerStandard: metadata2.review.candidateItemsPerStandard,
118818
118848
  selections: [...metadata2.review.selections],
118819
118849
  fulfillment: metadata2.review.fulfillment
118820
118850
  };
@@ -185960,7 +185990,7 @@ var init_timeback9 = __esm3(() => {
185960
185990
  ASSESSMENT_PURPOSES2 = ["end_of_course", "diagnostic", "review", "mastery"];
185961
185991
  TIMEBACK_ASSESSMENT_REVIEW_REQUEST_LIMITS2 = {
185962
185992
  standards: 20,
185963
- itemsPerStandard: 5,
185993
+ candidateItemsPerStandard: 2,
185964
185994
  standardFieldLength: 128
185965
185995
  };
185966
185996
  TIMEBACK_COURSE_DEFAULTS2 = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@playcademy/vite-plugin",
3
- "version": "1.2.1-beta.17",
3
+ "version": "1.2.1-beta.19",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -19,14 +19,14 @@
19
19
  "dependencies": {
20
20
  "archiver": "^7.0.1",
21
21
  "picocolors": "^1.1.1",
22
- "playcademy": "0.28.1-beta.17"
22
+ "playcademy": "0.28.1-beta.19"
23
23
  },
24
24
  "devDependencies": {
25
25
  "@electric-sql/pglite": "^0.3.16",
26
26
  "@inquirer/prompts": "^7.8.6",
27
27
  "@playcademy/constants": "0.0.1",
28
- "@playcademy/sandbox": "0.7.1-beta.16",
29
- "@playcademy/sdk": "0.16.1-beta.16",
28
+ "@playcademy/sandbox": "0.7.1-beta.18",
29
+ "@playcademy/sdk": "0.16.1-beta.18",
30
30
  "@playcademy/types": "0.0.1",
31
31
  "@playcademy/utils": "0.0.1",
32
32
  "@types/archiver": "^6.0.3",