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

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 +105 -69
  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.18",
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.17",
25925
25925
  description: "Local development server for Playcademy game development",
25926
25926
  type: "module",
25927
25927
  exports: {
@@ -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,20 @@ 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 committed = administeredItemIdentifiers(state.itemSubmissions);
34675
+ const expectedItemIndex = state.assessment.items.findIndex((item) => !committed.has(item.identifier));
34676
+ const previousItemIdentifier = state.itemSubmissions.at(-1)?.itemIdentifier;
34677
+ const previousItemIndex = state.assessment.items.findIndex((item) => item.identifier === previousItemIdentifier);
34678
+ const validOrder = state.purpose === "review" ? itemIndex > previousItemIndex : itemIndex === expectedItemIndex;
34679
+ if (itemIndex === -1 || !validOrder) {
34671
34680
  return {
34672
34681
  action: "reject",
34673
- failure: assessmentFlowViolation("Items must be submitted once and in presentation order.", {
34682
+ failure: assessmentFlowViolation(state.purpose === "review" ? "Items must be submitted once and in candidate order." : "Items must be submitted once and in presentation order.", {
34674
34683
  itemIdentifier: input.itemIdentifier,
34675
- expectedItemIdentifier: nextItem?.identifier ?? null,
34684
+ ...state.purpose === "review" ? { previousItemIdentifier: previousItemIdentifier ?? null } : {
34685
+ expectedItemIdentifier: state.assessment.items[expectedItemIndex]?.identifier ?? null
34686
+ },
34676
34687
  flow
34677
34688
  })
34678
34689
  };
@@ -34695,10 +34706,11 @@ function prepareAssessmentItemSubmission(state, input) {
34695
34706
  answered: Object.keys(responses[input.itemIdentifier] ?? {}).length > 0
34696
34707
  };
34697
34708
  }
34698
- function completeAssessmentItemSubmission(state, input, prepared, scoring) {
34709
+ function completeAssessmentItemSubmission(state, input, prepared, scoring, submittedAt) {
34699
34710
  const submission = {
34700
34711
  submissionId: input.submissionId,
34701
34712
  itemIdentifier: input.itemIdentifier,
34713
+ submittedAt,
34702
34714
  responseVersion: prepared.responseVersion,
34703
34715
  answered: prepared.answered,
34704
34716
  score: scoring.score,
@@ -34712,8 +34724,8 @@ function completeAssessmentItemSubmission(state, input, prepared, scoring) {
34712
34724
  };
34713
34725
  }
34714
34726
  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));
34727
+ const administered = administeredItemIdentifiers(itemSubmissions);
34728
+ const committedUpdate = Object.keys(update).find((identifier) => administered.has(identifier));
34717
34729
  return committedUpdate ? assessmentFlowViolation("A submitted item response cannot be edited.", {
34718
34730
  itemIdentifier: committedUpdate,
34719
34731
  flow: assessmentFlowForPurpose(purpose)
@@ -34721,11 +34733,18 @@ function assessmentResponseUpdateFlowFailure(purpose, itemSubmissions, update) {
34721
34733
  }
34722
34734
  function assessmentCompletionFlowFailure(purpose, itemSubmissions, itemCount) {
34723
34735
  const flow = assessmentFlowForPurpose(purpose);
34724
- return flow === "item-submit" && itemSubmissions.length !== itemCount ? assessmentFlowViolation("Every item must be submitted before the attempt can be completed.", {
34736
+ if (flow !== "item-submit") {
34737
+ return null;
34738
+ }
34739
+ const complete = purpose === "review" ? itemSubmissions.length > 0 && itemSubmissions.length <= itemCount : itemSubmissions.length === itemCount;
34740
+ if (complete) {
34741
+ return null;
34742
+ }
34743
+ return assessmentFlowViolation(purpose === "review" ? "At least one candidate must be administered before the review can be completed." : "Every item must be submitted before the attempt can be completed.", {
34725
34744
  flow,
34726
34745
  submittedItemCount: itemSubmissions.length,
34727
34746
  itemCount
34728
- }) : null;
34747
+ });
34729
34748
  }
34730
34749
  function selectAssessmentCourseCandidate(candidates, subjectSpecified) {
34731
34750
  if (candidates.length === 0) {
@@ -34901,15 +34920,15 @@ function canonicalRequestStandards(standards) {
34901
34920
  return [...canonical.values()].toSorted((left, right) => compareCodeUnits(assessmentStandardRefKey(left), assessmentStandardRefKey(right)));
34902
34921
  }
34903
34922
  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}`);
34923
+ const candidateItemsPerStandard = request.candidateItemsPerStandard ?? 1;
34924
+ if (!Number.isInteger(candidateItemsPerStandard) || candidateItemsPerStandard <= 0 || candidateItemsPerStandard > TIMEBACK_ASSESSMENT_REVIEW_REQUEST_LIMITS.candidateItemsPerStandard) {
34925
+ throw new RangeError(`candidateItemsPerStandard must be a positive integer at most ${TIMEBACK_ASSESSMENT_REVIEW_REQUEST_LIMITS.candidateItemsPerStandard}`);
34907
34926
  }
34908
34927
  const standards = canonicalRequestStandards(request.standards);
34909
34928
  if (standards.length === 0) {
34910
34929
  throw new Error("At least one review standard is required");
34911
34930
  }
34912
- return { standards, itemsPerStandard };
34931
+ return { standards, candidateItemsPerStandard };
34913
34932
  }
34914
34933
  function reviewRequestFingerprint(request, policy) {
34915
34934
  const normalized = normalizeReviewRequest(request);
@@ -34918,7 +34937,7 @@ function reviewRequestFingerprint(request, policy) {
34918
34937
  policy: {
34919
34938
  version: policy.version
34920
34939
  },
34921
- itemsPerStandard: normalized.itemsPerStandard,
34940
+ candidateItemsPerStandard: normalized.candidateItemsPerStandard,
34922
34941
  standardKeys: normalized.standards.map(assessmentStandardRefKey)
34923
34942
  });
34924
34943
  }
@@ -35018,7 +35037,7 @@ function rankedCandidatesByStandard(bank, exposures) {
35018
35037
  return byStandard;
35019
35038
  }
35020
35039
  function reviewSlots(request) {
35021
- return request.standards.flatMap((standard) => Array.from({ length: request.itemsPerStandard }, () => ({
35040
+ return request.standards.flatMap((standard) => Array.from({ length: request.candidateItemsPerStandard }, () => ({
35022
35041
  standard,
35023
35042
  standardKey: assessmentStandardRefKey(standard)
35024
35043
  })));
@@ -35122,7 +35141,7 @@ function preferredDistinctMatching(slots, candidatesByStandard) {
35122
35141
  return new Map(slotItemEdges.filter((candidate) => candidate.edge.capacity === 0).map((candidate) => [candidate.slotIndex, candidate.itemIdentifier]));
35123
35142
  }
35124
35143
  function fulfillment(request, candidatesByStandard, selections) {
35125
- const requestedItemCount = request.standards.length * request.itemsPerStandard;
35144
+ const requestedItemCount = request.standards.length * request.candidateItemsPerStandard;
35126
35145
  const selectedByStandard = new Map;
35127
35146
  for (const selection of selections) {
35128
35147
  const key = assessmentStandardRefKey(selection.standard);
@@ -35131,10 +35150,10 @@ function fulfillment(request, candidatesByStandard, selections) {
35131
35150
  const shortages = request.standards.flatMap((standard) => {
35132
35151
  const key = assessmentStandardRefKey(standard);
35133
35152
  const selected = selectedByStandard.get(key) ?? 0;
35134
- return selected < request.itemsPerStandard ? [
35153
+ return selected < request.candidateItemsPerStandard ? [
35135
35154
  {
35136
35155
  standard,
35137
- requested: request.itemsPerStandard,
35156
+ requested: request.candidateItemsPerStandard,
35138
35157
  selected,
35139
35158
  eligible: candidatesByStandard.get(key)?.length ?? 0
35140
35159
  }
@@ -35176,9 +35195,9 @@ function projectReviewAssessment(assessment, bank, selections) {
35176
35195
  throw new Error(`Review bank ${bank.bankRevision} was built from content revision ${bank.sourceContentRevision}, not ${assessment.contentRevision}`);
35177
35196
  }
35178
35197
  const selectedIdentifiers = new Set(selections.map((selection) => selection.itemIdentifier));
35179
- const knownIdentifiers = new Set(assessment.items.map((item) => item.identifier));
35198
+ const itemByIdentifier = new Map(assessment.items.map((item) => [item.identifier, item]));
35180
35199
  const indexedItems = new Map(bank.items.map((item) => [item.itemIdentifier, item]));
35181
- const missingIdentifiers = [...selectedIdentifiers].filter((identifier) => !knownIdentifiers.has(identifier) || !indexedItems.has(identifier));
35200
+ const missingIdentifiers = [...selectedIdentifiers].filter((identifier) => !itemByIdentifier.has(identifier) || !indexedItems.has(identifier));
35182
35201
  if (missingIdentifiers.length > 0) {
35183
35202
  throw new Error(`Selected review items are absent from the pinned bank: ${missingIdentifiers.join(", ")}`);
35184
35203
  }
@@ -35194,7 +35213,7 @@ function projectReviewAssessment(assessment, bank, selections) {
35194
35213
  return structuredClone({
35195
35214
  ...assessment,
35196
35215
  contentRevision: bank.bankRevision,
35197
- items: assessment.items.filter((item) => selectedIdentifiers.has(item.identifier))
35216
+ items: selections.map((selection) => itemByIdentifier.get(selection.itemIdentifier))
35198
35217
  });
35199
35218
  }
35200
35219
  function assessmentItemCorrectness(verdicts) {
@@ -35253,10 +35272,10 @@ function isReviewItemOutcome(value) {
35253
35272
  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
35273
  }
35255
35274
  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");
35275
+ 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
35276
  }
35258
35277
  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);
35278
+ 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
35279
  }
35261
35280
  function isMasteryAttemptMetadata(value) {
35262
35281
  return isRecord(value) && typeof value.requestFingerprint === "string" && isAssessmentStandardRef(value.standard);
@@ -35626,7 +35645,7 @@ var init_assessment_runtime = __esm(() => {
35626
35645
  StartAssessmentBaseSchema.extend({
35627
35646
  purpose: exports_external.literal("review"),
35628
35647
  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()
35648
+ candidateItemsPerStandard: exports_external.number().int().positive().max(TIMEBACK_ASSESSMENT_REVIEW_REQUEST_LIMITS.candidateItemsPerStandard).optional()
35630
35649
  }),
35631
35650
  StartAssessmentBaseSchema.extend({
35632
35651
  purpose: exports_external.literal("mastery"),
@@ -116804,6 +116823,10 @@ var init_timeback4 = __esm(() => {
116804
116823
  init_assessment_runtime2();
116805
116824
  });
116806
116825
  function buildReviewItemResultMetadata(input) {
116826
+ const administration = itemAdministration(input.metadata.itemSubmissions, input.selection.itemIdentifier);
116827
+ if (!administration) {
116828
+ throw new Error(`Cannot create a review result for unadministered candidate ${input.selection.itemIdentifier}`);
116829
+ }
116807
116830
  return {
116808
116831
  version: PLAYCADEMY_ASSESSMENT_ITEM_RESULT_METADATA_VERSION,
116809
116832
  parentAttemptId: input.parentAttemptId,
@@ -116813,7 +116836,7 @@ function buildReviewItemResultMetadata(input) {
116813
116836
  bankRevision: input.metadata.review.bankRevision,
116814
116837
  itemIdentifier: input.selection.itemIdentifier,
116815
116838
  standard: input.selection.standard,
116816
- deliveredAt: input.metadata.startedAt,
116839
+ deliveredAt: administration.submittedAt,
116817
116840
  responseVersion: input.responseVersion ?? input.metadata.responseVersion,
116818
116841
  responses: input.metadata.responses[input.selection.itemIdentifier] ?? {},
116819
116842
  ...input.submissionId ? { submissionId: input.submissionId } : {},
@@ -116868,7 +116891,8 @@ function resolveReviewChildResult(input) {
116868
116891
  }
116869
116892
  const childMetadata = playcademyAssessmentItemResultMetadata(input.childResult.metadata);
116870
116893
  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)) {
116894
+ const administration = itemAdministration(input.parentMetadata.itemSubmissions, input.selection.itemIdentifier);
116895
+ 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
116896
  return { action: "restore", reason: "incompatible" };
116873
116897
  }
116874
116898
  return input.childResult.status === "active" ? { action: "reuse", metadata: childMetadata } : { action: "restore", reason: "inactive" };
@@ -117556,7 +117580,7 @@ class TimebackAssessmentRuntimeService {
117556
117580
  requestFingerprint,
117557
117581
  bankRevision: source.bank.bankRevision,
117558
117582
  standards: [...normalizedRequest.standards],
117559
- itemsPerStandard: normalizedRequest.itemsPerStandard,
117583
+ candidateItemsPerStandard: normalizedRequest.candidateItemsPerStandard,
117560
117584
  selections: [...selected.selections],
117561
117585
  fulfillment: selected.fulfillment
117562
117586
  }
@@ -117688,9 +117712,6 @@ class TimebackAssessmentRuntimeService {
117688
117712
  updatedAt: new Date().toISOString()
117689
117713
  };
117690
117714
  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
117715
  return {
117695
117716
  attemptId: attempt.result.sourcedId,
117696
117717
  responseVersion: metadata2.responseVersion,
@@ -117758,13 +117779,14 @@ class TimebackAssessmentRuntimeService {
117758
117779
  if (!preview.scoring) {
117759
117780
  throw new Error("Prepared item submission is missing its score");
117760
117781
  }
117761
- const completed = completeAssessmentItemSubmission(attempt.metadata, input, transition, preview.scoring);
117782
+ const submittedAt = new Date().toISOString();
117783
+ const completed = completeAssessmentItemSubmission(attempt.metadata, input, transition, preview.scoring, submittedAt);
117762
117784
  const metadata2 = {
117763
117785
  ...attempt.metadata,
117764
117786
  responses: completed.responses,
117765
117787
  responseVersion: completed.responseVersion,
117766
117788
  itemSubmissions: completed.itemSubmissions,
117767
- updatedAt: new Date().toISOString()
117789
+ updatedAt: submittedAt
117768
117790
  };
117769
117791
  await this.putResultMetadata(attempt.result, metadata2);
117770
117792
  return {
@@ -117869,12 +117891,13 @@ class TimebackAssessmentRuntimeService {
117869
117891
  async scoreSubmission(attempt) {
117870
117892
  const assessment = await this.loadAttemptAssessment(attempt.metadata);
117871
117893
  validateAssessmentResponses(assessment, attempt.metadata.responses);
117872
- const itemResults = await this.scoreItems(assessment, attempt.metadata.responses);
117894
+ const administeredItems = attempt.metadata.purpose === "review" ? administeredItemIdentifiers(attempt.metadata.itemSubmissions) : undefined;
117895
+ const itemResults = await this.scoreItems(assessment, attempt.metadata.responses, administeredItems);
117873
117896
  return {
117874
117897
  responseVersion: attempt.metadata.responseVersion,
117875
117898
  assessment,
117876
117899
  itemResults,
117877
- score: aggregateAssessmentScore(itemResults.map((result) => result.score.earned), assessmentPossibleScore(assessment)),
117900
+ score: aggregateAssessmentScore(itemResults.map((result) => result.score.earned), itemResults.reduce((total, result) => total + result.score.possible, 0)),
117878
117901
  correctQuestions: countCorrectQuestions(itemResults.map((result) => result.isCorrect))
117879
117902
  };
117880
117903
  }
@@ -117967,7 +117990,7 @@ class TimebackAssessmentRuntimeService {
117967
117990
  submissionId: input.submissionId,
117968
117991
  timestamp: timestamp6,
117969
117992
  testName: assessment.title,
117970
- totalQuestions: assessment.items.length,
117993
+ totalQuestions: itemResults.length,
117971
117994
  correctQuestions,
117972
117995
  ...input.session ? { session: input.session } : {},
117973
117996
  ...reviewOutcomes ? { itemOutcomes: reviewOutcomes } : {}
@@ -117998,7 +118021,7 @@ class TimebackAssessmentRuntimeService {
117998
118021
  testIdentifier: assessment.identifier,
117999
118022
  testName: assessment.title,
118000
118023
  score,
118001
- totalQuestions: assessment.items.length,
118024
+ totalQuestions: itemResults.length,
118002
118025
  correctQuestions,
118003
118026
  submissionId: input.submissionId,
118004
118027
  gameId: params.gameId,
@@ -118542,11 +118565,21 @@ class TimebackAssessmentRuntimeService {
118542
118565
  if (physicalItems.size !== input.metadata.review.selections.length) {
118543
118566
  throw new Error("MVP review selection must use each physical item at most once");
118544
118567
  }
118568
+ const administeredItems = administeredItemIdentifiers(input.metadata.itemSubmissions);
118545
118569
  await runWithConcurrency(input.metadata.review.selections, TimebackAssessmentRuntimeService.SCORING_CONCURRENCY, async (selection) => {
118546
118570
  const item = itemByIdentifier.get(selection.itemIdentifier);
118547
118571
  if (!item) {
118548
118572
  throw new Error(`Review assessment is missing selected item ${selection.itemIdentifier}`);
118549
118573
  }
118574
+ if (!administeredItems.has(selection.itemIdentifier)) {
118575
+ await this.ensureReviewQuestionLineItem({
118576
+ parentLineItemId: input.result.assessmentLineItem.sourcedId,
118577
+ integration: input.integration,
118578
+ bank: input.source.bank,
118579
+ item
118580
+ });
118581
+ return;
118582
+ }
118550
118583
  const childLineItemId = await reviewQuestionLineItemId(input.result.assessmentLineItem.sourcedId, item.identifier);
118551
118584
  const childResultId = await reviewQuestionResultId(input.result.sourcedId, childLineItemId);
118552
118585
  const streamedChildResult = input.existingChildResults.get(childResultId);
@@ -118593,33 +118626,35 @@ class TimebackAssessmentRuntimeService {
118593
118626
  });
118594
118627
  });
118595
118628
  }
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
- });
118629
+ async putReviewChildResponse(result, metadata2, itemIdentifier) {
118630
+ const selection = metadata2.review.selections.find((candidate) => candidate.itemIdentifier === itemIdentifier);
118631
+ if (!selection) {
118632
+ return;
118633
+ }
118634
+ try {
118635
+ const childLineItemId = await reviewQuestionLineItemId(result.assessmentLineItem.sourcedId, selection.itemIdentifier);
118636
+ const childResultId = await reviewQuestionResultId(result.sourcedId, childLineItemId);
118637
+ const childMetadata = buildReviewItemResultMetadata({
118638
+ metadata: metadata2,
118639
+ selection,
118640
+ parentAttemptId: result.sourcedId
118641
+ });
118642
+ await this.requireClient().api.oneroster.assessmentResults.upsert(childResultId, buildOpenReviewChildResult({
118643
+ childLineItemId,
118644
+ student: result.student,
118645
+ metadata: childMetadata
118646
+ }));
118647
+ } catch (error88) {
118648
+ addEvent("assessment.review_child_response_projection_failed", {
118649
+ "app.assessment.attempt_id": result.sourcedId,
118650
+ "app.assessment.qti_item_identifier": selection.itemIdentifier,
118651
+ "exception.type": errorType(error88),
118652
+ "app.error.message": errorMessage2(error88)
118653
+ });
118654
+ }
118620
118655
  }
118621
118656
  async projectReviewItemResponse(params, projection) {
118622
- await this.putReviewChildResponses(projection.result, projection.metadata, new Set([projection.itemIdentifier]));
118657
+ await this.putReviewChildResponse(projection.result, projection.metadata, projection.itemIdentifier);
118623
118658
  try {
118624
118659
  await this.crossAttemptLockBarrier(params.attemptId);
118625
118660
  const latest = await this.peekAttempt(params);
@@ -118698,7 +118733,8 @@ class TimebackAssessmentRuntimeService {
118698
118733
  }
118699
118734
  async finalizeReviewChildResults(input) {
118700
118735
  const resultByItem = new Map(input.itemResults.map((itemResult) => [itemResult.itemIdentifier, itemResult]));
118701
- return runWithConcurrency(input.metadata.review.selections, TimebackAssessmentRuntimeService.SCORING_CONCURRENCY, async (selection) => {
118736
+ const administeredItems = administeredItemIdentifiers(input.metadata.itemSubmissions);
118737
+ return runWithConcurrency(input.metadata.review.selections.filter((selection) => administeredItems.has(selection.itemIdentifier)), TimebackAssessmentRuntimeService.SCORING_CONCURRENCY, async (selection) => {
118702
118738
  const itemResult = resultByItem.get(selection.itemIdentifier);
118703
118739
  if (!itemResult) {
118704
118740
  throw new Error(`Scoring omitted selected review item ${selection.itemIdentifier}`);
@@ -118814,7 +118850,7 @@ class TimebackAssessmentRuntimeService {
118814
118850
  kind: "standards-review",
118815
118851
  purpose: metadata2.purpose,
118816
118852
  standards: [...metadata2.review.standards],
118817
- itemsPerStandard: metadata2.review.itemsPerStandard,
118853
+ candidateItemsPerStandard: metadata2.review.candidateItemsPerStandard,
118818
118854
  selections: [...metadata2.review.selections],
118819
118855
  fulfillment: metadata2.review.fulfillment
118820
118856
  };
@@ -185960,7 +185996,7 @@ var init_timeback9 = __esm3(() => {
185960
185996
  ASSESSMENT_PURPOSES2 = ["end_of_course", "diagnostic", "review", "mastery"];
185961
185997
  TIMEBACK_ASSESSMENT_REVIEW_REQUEST_LIMITS2 = {
185962
185998
  standards: 20,
185963
- itemsPerStandard: 5,
185999
+ candidateItemsPerStandard: 2,
185964
186000
  standardFieldLength: 128
185965
186001
  };
185966
186002
  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.18",
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.18"
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.17",
29
+ "@playcademy/sdk": "0.16.1-beta.17",
30
30
  "@playcademy/types": "0.0.1",
31
31
  "@playcademy/utils": "0.0.1",
32
32
  "@types/archiver": "^6.0.3",