@playcademy/sandbox 0.9.1-beta.1 → 0.10.1-beta.1

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 (3) hide show
  1. package/dist/cli.js +43 -33
  2. package/dist/server.js +43 -33
  3. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -1128,7 +1128,7 @@ var package_default;
1128
1128
  var init_package = __esm(() => {
1129
1129
  package_default = {
1130
1130
  name: "@playcademy/sandbox",
1131
- version: "0.9.1-beta.1",
1131
+ version: "0.10.1-beta.1",
1132
1132
  description: "Local development server for Playcademy game development",
1133
1133
  type: "module",
1134
1134
  exports: {
@@ -9562,6 +9562,21 @@ function assessmentReviewBankShortage(fulfillment) {
9562
9562
  function assessmentFlowForPurpose(purpose) {
9563
9563
  return purpose === "review" ? "item-submit" : "attempt-submit";
9564
9564
  }
9565
+ function assessmentItemOutcomes(submissions, standardForItem) {
9566
+ return submissions.map((submission) => {
9567
+ const standard = standardForItem(submission.itemIdentifier);
9568
+ if (!standard) {
9569
+ throw new Error(`Missing outcome standard for item ${submission.itemIdentifier}`);
9570
+ }
9571
+ return {
9572
+ itemIdentifier: submission.itemIdentifier,
9573
+ standard: { ...standard },
9574
+ answered: submission.answered,
9575
+ score: { ...submission.score },
9576
+ isCorrect: submission.isCorrect
9577
+ };
9578
+ });
9579
+ }
9565
9580
  function isObject(value) {
9566
9581
  return typeof value === "object" && value !== null;
9567
9582
  }
@@ -10730,7 +10745,7 @@ function completeAssessmentTranscript(input) {
10730
10745
  }
10731
10746
  if (input.terminal) {
10732
10747
  const count = itemSubmissions.length;
10733
- const conventionalComplete = assessmentFlowForPurpose(input.purpose) === "item-submit" ? count > 0 : count === input.itemCount;
10748
+ const conventionalComplete = assessmentFlowForPurpose(input.purpose) === "item-submit" ? count > 0 : count === input.itemCount && (input.purpose !== "mastery" || count > 0);
10734
10749
  const complete = state ? state.status === "ready-to-complete" && count > 0 : conventionalComplete;
10735
10750
  if (!complete) {
10736
10751
  throw new AssessmentTranscriptError("A complete administered transcript is required for submission", count);
@@ -10738,12 +10753,6 @@ function completeAssessmentTranscript(input) {
10738
10753
  }
10739
10754
  return { itemSubmissions, ledger, state };
10740
10755
  }
10741
- function administeredItemIdentifiers(itemSubmissions) {
10742
- return new Set(itemSubmissions.map((submission) => submission.itemIdentifier));
10743
- }
10744
- function itemAdministration(itemSubmissions, itemIdentifier) {
10745
- return itemSubmissions.find((submission) => submission.itemIdentifier === itemIdentifier);
10746
- }
10747
10756
  function selectAssessmentCourseCandidate(candidates, subjectSpecified) {
10748
10757
  if (candidates.length === 0) {
10749
10758
  return { outcome: "none" };
@@ -11570,7 +11579,7 @@ function isReviewFulfillment(value) {
11570
11579
  const shortagesValid = value.shortages.every((shortage) => isRecord(shortage) && isAssessmentStandardRef(shortage.standard) && Number.isInteger(shortage.requested) && Number.isInteger(shortage.selected) && Number.isInteger(shortage.eligible));
11571
11580
  return shortagesValid && (value.status === "complete" && value.shortages.length === 0 || value.status === "partial" && value.shortages.length > 0);
11572
11581
  }
11573
- function isReviewItemOutcome(value) {
11582
+ function isAssessmentItemOutcome(value) {
11574
11583
  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");
11575
11584
  }
11576
11585
  function isAssessmentItemSubmission(value) {
@@ -11661,7 +11670,7 @@ function isCompletionMetadata(value, purpose, routedDiagnostic) {
11661
11670
  if (!isRecord(value) || typeof value.testName !== "string" || value.courseName !== undefined && typeof value.courseName !== "string" || !Number.isInteger(value.correctQuestions) || value.session !== undefined && !isAssessmentSessionTiming(value.session) || value.totalQuestions !== undefined && (!Number.isInteger(value.totalQuestions) || Number(value.totalQuestions) < 0)) {
11662
11671
  return false;
11663
11672
  }
11664
- const itemOutcomesValid = value.itemOutcomes === undefined || purpose === "review" && Array.isArray(value.itemOutcomes) && value.itemOutcomes.every(isReviewItemOutcome);
11673
+ const itemOutcomesValid = value.itemOutcomes === undefined || purpose === "review" && Array.isArray(value.itemOutcomes) && value.itemOutcomes.every(isAssessmentItemOutcome);
11665
11674
  const diagnosticTracksValid = value.diagnosticTracks === undefined || routedDiagnostic && Array.isArray(value.diagnosticTracks) && value.diagnosticTracks.every(isDiagnosticTrackResult);
11666
11675
  return itemOutcomesValid && diagnosticTracksValid && (!routedDiagnostic || value.itemOutcomes === undefined && Array.isArray(value.diagnosticTracks) && value.diagnosticTracks.length > 0);
11667
11676
  }
@@ -96840,10 +96849,12 @@ function compileResponseProcessing(item, itemIdentifier, maxScore, declarations)
96840
96849
  if (!onlyAttributes(processing, []) || !onlyWhitespaceText(processing.children) || processingRules.length === 0 || processingRules.some((rule) => rule.localName !== "qti-response-condition")) {
96841
96850
  return fallback(itemIdentifier, maxScore, "unsupported-response-processing");
96842
96851
  }
96843
- if (processingRules.length === 1 && declarations.size === 1 && scoreDefaultIsZero(item) && feedbackOutcomeIsDeclared(item)) {
96852
+ if (processingRules.length === 1 && declarations.size === 1 && feedbackOutcomeIsDeclared(item)) {
96844
96853
  const declaration = [...declarations.values()][0];
96845
96854
  const rule = binaryBranchRule(processingRules[0], declaration);
96846
- if (rule) {
96855
+ const scoreIsDeclared = childElements(item, "qti-outcome-declaration").some((outcome) => outcome.attributes.identifier?.trim().toUpperCase() === "SCORE");
96856
+ const completeIdentifierMatch = rule?.comparison.kind === "match" && rule.baseType === "identifier" && (rule.cardinality === "single" || rule.cardinality === "multiple");
96857
+ if (rule && (scoreDefaultIsZero(item) || !scoreIsDeclared && completeIdentifierMatch)) {
96847
96858
  return platformResult(itemIdentifier, maxScore, [rule]);
96848
96859
  }
96849
96860
  }
@@ -99791,7 +99802,7 @@ function scoringSchema(identity) {
99791
99802
  if (identity.kind === "assessment") {
99792
99803
  return exports_external.object({
99793
99804
  items: assessmentScoringArtifactItemsSchema2,
99794
- reviewBank: exports_external.undefined(),
99805
+ reviewBank: exports_external.undefined().optional(),
99795
99806
  diagnostic: diagnosticContextSchema.optional()
99796
99807
  }).strict();
99797
99808
  }
@@ -99805,7 +99816,7 @@ function scoringSchema(identity) {
99805
99816
  standards: exports_external.array(exports_external.unknown())
99806
99817
  }).strict())
99807
99818
  }).strict(),
99808
- diagnostic: exports_external.undefined()
99819
+ diagnostic: exports_external.undefined().optional()
99809
99820
  }).strict().refine((payload) => payload.reviewBank.items.length === payload.items.length && payload.reviewBank.items.every((item, index2) => item.itemIdentifier === payload.items[index2]?.itemIdentifier));
99810
99821
  }
99811
99822
  function assertPlayableAssessmentArtifact(payload, identity) {
@@ -100011,7 +100022,7 @@ var init_assessment_artifact_runtime_util = __esm(() => {
100011
100022
  }).strict();
100012
100023
  diagnosticContextSchema = exports_external.object({
100013
100024
  routingRevision: trimmedNonEmptyString3,
100014
- routingManifest: exports_external.record(exports_external.unknown())
100025
+ routingManifest: exports_external.record(exports_external.string(), exports_external.unknown())
100015
100026
  }).strict();
100016
100027
  });
100017
100028
 
@@ -100386,6 +100397,17 @@ function buildAssessmentSubmitResult(input) {
100386
100397
  const itemOutcomes = input.metadata.completion?.itemOutcomes;
100387
100398
  return itemOutcomes ? { ...base, purpose: input.metadata.purpose, itemOutcomes } : null;
100388
100399
  }
100400
+ if (input.metadata.purpose === "mastery") {
100401
+ const { itemSubmissions, mastery, completion } = input.metadata;
100402
+ if (itemSubmissions.length === 0 || completion?.totalQuestions !== undefined && itemSubmissions.length !== completion.totalQuestions) {
100403
+ return null;
100404
+ }
100405
+ return {
100406
+ ...base,
100407
+ purpose: "mastery",
100408
+ itemOutcomes: assessmentItemOutcomes(itemSubmissions, () => mastery.standard)
100409
+ };
100410
+ }
100389
100411
  return { ...base, purpose: input.metadata.purpose };
100390
100412
  }
100391
100413
  function assessmentAwardFromResult(result, metadata2) {
@@ -103392,22 +103414,11 @@ async function resumeAssessmentTranscript(input) {
103392
103414
  }
103393
103415
  }
103394
103416
  function assessmentReviewTranscriptOutcomes(input) {
103395
- const resultByItem = new Map(input.itemResults.map((item) => [item.itemIdentifier, item]));
103396
- const administered = administeredItemIdentifiers(input.metadata.itemSubmissions);
103397
- return input.metadata.review.selections.filter((selection) => administered.has(selection.itemIdentifier)).map((selection) => {
103398
- const result = resultByItem.get(selection.itemIdentifier);
103399
- const submission = itemAdministration(input.metadata.itemSubmissions, selection.itemIdentifier);
103400
- if (!result || !submission) {
103401
- throw new Error(`Scoring omitted selected review item ${selection.itemIdentifier}`);
103402
- }
103403
- return {
103404
- itemIdentifier: selection.itemIdentifier,
103405
- standard: selection.standard,
103406
- answered: submission.answered,
103407
- score: result.score,
103408
- isCorrect: result.isCorrect
103409
- };
103410
- });
103417
+ const standards = new Map(input.metadata.review.selections.map((selection) => [
103418
+ selection.itemIdentifier,
103419
+ selection.standard
103420
+ ]));
103421
+ return assessmentItemOutcomes(input.metadata.itemSubmissions, (identifier) => standards.get(identifier));
103411
103422
  }
103412
103423
  function assessmentReviewTranscriptExposures(input) {
103413
103424
  const exposures = [];
@@ -104459,8 +104470,7 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
104459
104470
  }, transcript, true, db2);
104460
104471
  const diagnostic = this.isPlatformRoutedDiagnosticMetadata(graded.metadata) ? summarizeDiagnosticSubmission(graded.metadata, graded.manifest) : null;
104461
104472
  const itemOutcomes = graded.metadata.purpose === "review" ? assessmentReviewTranscriptOutcomes({
104462
- metadata: graded.metadata,
104463
- itemResults: graded.itemResults
104473
+ metadata: graded.metadata
104464
104474
  }) : undefined;
104465
104475
  const courseName = attempt.metadata.courseName ?? "Game Course";
104466
104476
  const timestamp6 = new Date().toISOString();
package/dist/server.js CHANGED
@@ -1127,7 +1127,7 @@ var package_default;
1127
1127
  var init_package = __esm(() => {
1128
1128
  package_default = {
1129
1129
  name: "@playcademy/sandbox",
1130
- version: "0.9.1-beta.1",
1130
+ version: "0.10.1-beta.1",
1131
1131
  description: "Local development server for Playcademy game development",
1132
1132
  type: "module",
1133
1133
  exports: {
@@ -9561,6 +9561,21 @@ function assessmentReviewBankShortage(fulfillment) {
9561
9561
  function assessmentFlowForPurpose(purpose) {
9562
9562
  return purpose === "review" ? "item-submit" : "attempt-submit";
9563
9563
  }
9564
+ function assessmentItemOutcomes(submissions, standardForItem) {
9565
+ return submissions.map((submission) => {
9566
+ const standard = standardForItem(submission.itemIdentifier);
9567
+ if (!standard) {
9568
+ throw new Error(`Missing outcome standard for item ${submission.itemIdentifier}`);
9569
+ }
9570
+ return {
9571
+ itemIdentifier: submission.itemIdentifier,
9572
+ standard: { ...standard },
9573
+ answered: submission.answered,
9574
+ score: { ...submission.score },
9575
+ isCorrect: submission.isCorrect
9576
+ };
9577
+ });
9578
+ }
9564
9579
  function isObject(value) {
9565
9580
  return typeof value === "object" && value !== null;
9566
9581
  }
@@ -10729,7 +10744,7 @@ function completeAssessmentTranscript(input) {
10729
10744
  }
10730
10745
  if (input.terminal) {
10731
10746
  const count = itemSubmissions.length;
10732
- const conventionalComplete = assessmentFlowForPurpose(input.purpose) === "item-submit" ? count > 0 : count === input.itemCount;
10747
+ const conventionalComplete = assessmentFlowForPurpose(input.purpose) === "item-submit" ? count > 0 : count === input.itemCount && (input.purpose !== "mastery" || count > 0);
10733
10748
  const complete = state ? state.status === "ready-to-complete" && count > 0 : conventionalComplete;
10734
10749
  if (!complete) {
10735
10750
  throw new AssessmentTranscriptError("A complete administered transcript is required for submission", count);
@@ -10737,12 +10752,6 @@ function completeAssessmentTranscript(input) {
10737
10752
  }
10738
10753
  return { itemSubmissions, ledger, state };
10739
10754
  }
10740
- function administeredItemIdentifiers(itemSubmissions) {
10741
- return new Set(itemSubmissions.map((submission) => submission.itemIdentifier));
10742
- }
10743
- function itemAdministration(itemSubmissions, itemIdentifier) {
10744
- return itemSubmissions.find((submission) => submission.itemIdentifier === itemIdentifier);
10745
- }
10746
10755
  function selectAssessmentCourseCandidate(candidates, subjectSpecified) {
10747
10756
  if (candidates.length === 0) {
10748
10757
  return { outcome: "none" };
@@ -11569,7 +11578,7 @@ function isReviewFulfillment(value) {
11569
11578
  const shortagesValid = value.shortages.every((shortage) => isRecord(shortage) && isAssessmentStandardRef(shortage.standard) && Number.isInteger(shortage.requested) && Number.isInteger(shortage.selected) && Number.isInteger(shortage.eligible));
11570
11579
  return shortagesValid && (value.status === "complete" && value.shortages.length === 0 || value.status === "partial" && value.shortages.length > 0);
11571
11580
  }
11572
- function isReviewItemOutcome(value) {
11581
+ function isAssessmentItemOutcome(value) {
11573
11582
  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");
11574
11583
  }
11575
11584
  function isAssessmentItemSubmission(value) {
@@ -11660,7 +11669,7 @@ function isCompletionMetadata(value, purpose, routedDiagnostic) {
11660
11669
  if (!isRecord(value) || typeof value.testName !== "string" || value.courseName !== undefined && typeof value.courseName !== "string" || !Number.isInteger(value.correctQuestions) || value.session !== undefined && !isAssessmentSessionTiming(value.session) || value.totalQuestions !== undefined && (!Number.isInteger(value.totalQuestions) || Number(value.totalQuestions) < 0)) {
11661
11670
  return false;
11662
11671
  }
11663
- const itemOutcomesValid = value.itemOutcomes === undefined || purpose === "review" && Array.isArray(value.itemOutcomes) && value.itemOutcomes.every(isReviewItemOutcome);
11672
+ const itemOutcomesValid = value.itemOutcomes === undefined || purpose === "review" && Array.isArray(value.itemOutcomes) && value.itemOutcomes.every(isAssessmentItemOutcome);
11664
11673
  const diagnosticTracksValid = value.diagnosticTracks === undefined || routedDiagnostic && Array.isArray(value.diagnosticTracks) && value.diagnosticTracks.every(isDiagnosticTrackResult);
11665
11674
  return itemOutcomesValid && diagnosticTracksValid && (!routedDiagnostic || value.itemOutcomes === undefined && Array.isArray(value.diagnosticTracks) && value.diagnosticTracks.length > 0);
11666
11675
  }
@@ -96839,10 +96848,12 @@ function compileResponseProcessing(item, itemIdentifier, maxScore, declarations)
96839
96848
  if (!onlyAttributes(processing, []) || !onlyWhitespaceText(processing.children) || processingRules.length === 0 || processingRules.some((rule) => rule.localName !== "qti-response-condition")) {
96840
96849
  return fallback(itemIdentifier, maxScore, "unsupported-response-processing");
96841
96850
  }
96842
- if (processingRules.length === 1 && declarations.size === 1 && scoreDefaultIsZero(item) && feedbackOutcomeIsDeclared(item)) {
96851
+ if (processingRules.length === 1 && declarations.size === 1 && feedbackOutcomeIsDeclared(item)) {
96843
96852
  const declaration = [...declarations.values()][0];
96844
96853
  const rule = binaryBranchRule(processingRules[0], declaration);
96845
- if (rule) {
96854
+ const scoreIsDeclared = childElements(item, "qti-outcome-declaration").some((outcome) => outcome.attributes.identifier?.trim().toUpperCase() === "SCORE");
96855
+ const completeIdentifierMatch = rule?.comparison.kind === "match" && rule.baseType === "identifier" && (rule.cardinality === "single" || rule.cardinality === "multiple");
96856
+ if (rule && (scoreDefaultIsZero(item) || !scoreIsDeclared && completeIdentifierMatch)) {
96846
96857
  return platformResult(itemIdentifier, maxScore, [rule]);
96847
96858
  }
96848
96859
  }
@@ -99790,7 +99801,7 @@ function scoringSchema(identity) {
99790
99801
  if (identity.kind === "assessment") {
99791
99802
  return exports_external.object({
99792
99803
  items: assessmentScoringArtifactItemsSchema2,
99793
- reviewBank: exports_external.undefined(),
99804
+ reviewBank: exports_external.undefined().optional(),
99794
99805
  diagnostic: diagnosticContextSchema.optional()
99795
99806
  }).strict();
99796
99807
  }
@@ -99804,7 +99815,7 @@ function scoringSchema(identity) {
99804
99815
  standards: exports_external.array(exports_external.unknown())
99805
99816
  }).strict())
99806
99817
  }).strict(),
99807
- diagnostic: exports_external.undefined()
99818
+ diagnostic: exports_external.undefined().optional()
99808
99819
  }).strict().refine((payload) => payload.reviewBank.items.length === payload.items.length && payload.reviewBank.items.every((item, index2) => item.itemIdentifier === payload.items[index2]?.itemIdentifier));
99809
99820
  }
99810
99821
  function assertPlayableAssessmentArtifact(payload, identity) {
@@ -100010,7 +100021,7 @@ var init_assessment_artifact_runtime_util = __esm(() => {
100010
100021
  }).strict();
100011
100022
  diagnosticContextSchema = exports_external.object({
100012
100023
  routingRevision: trimmedNonEmptyString3,
100013
- routingManifest: exports_external.record(exports_external.unknown())
100024
+ routingManifest: exports_external.record(exports_external.string(), exports_external.unknown())
100014
100025
  }).strict();
100015
100026
  });
100016
100027
 
@@ -100385,6 +100396,17 @@ function buildAssessmentSubmitResult(input) {
100385
100396
  const itemOutcomes = input.metadata.completion?.itemOutcomes;
100386
100397
  return itemOutcomes ? { ...base, purpose: input.metadata.purpose, itemOutcomes } : null;
100387
100398
  }
100399
+ if (input.metadata.purpose === "mastery") {
100400
+ const { itemSubmissions, mastery, completion } = input.metadata;
100401
+ if (itemSubmissions.length === 0 || completion?.totalQuestions !== undefined && itemSubmissions.length !== completion.totalQuestions) {
100402
+ return null;
100403
+ }
100404
+ return {
100405
+ ...base,
100406
+ purpose: "mastery",
100407
+ itemOutcomes: assessmentItemOutcomes(itemSubmissions, () => mastery.standard)
100408
+ };
100409
+ }
100388
100410
  return { ...base, purpose: input.metadata.purpose };
100389
100411
  }
100390
100412
  function assessmentAwardFromResult(result, metadata2) {
@@ -103391,22 +103413,11 @@ async function resumeAssessmentTranscript(input) {
103391
103413
  }
103392
103414
  }
103393
103415
  function assessmentReviewTranscriptOutcomes(input) {
103394
- const resultByItem = new Map(input.itemResults.map((item) => [item.itemIdentifier, item]));
103395
- const administered = administeredItemIdentifiers(input.metadata.itemSubmissions);
103396
- return input.metadata.review.selections.filter((selection) => administered.has(selection.itemIdentifier)).map((selection) => {
103397
- const result = resultByItem.get(selection.itemIdentifier);
103398
- const submission = itemAdministration(input.metadata.itemSubmissions, selection.itemIdentifier);
103399
- if (!result || !submission) {
103400
- throw new Error(`Scoring omitted selected review item ${selection.itemIdentifier}`);
103401
- }
103402
- return {
103403
- itemIdentifier: selection.itemIdentifier,
103404
- standard: selection.standard,
103405
- answered: submission.answered,
103406
- score: result.score,
103407
- isCorrect: result.isCorrect
103408
- };
103409
- });
103416
+ const standards = new Map(input.metadata.review.selections.map((selection) => [
103417
+ selection.itemIdentifier,
103418
+ selection.standard
103419
+ ]));
103420
+ return assessmentItemOutcomes(input.metadata.itemSubmissions, (identifier) => standards.get(identifier));
103410
103421
  }
103411
103422
  function assessmentReviewTranscriptExposures(input) {
103412
103423
  const exposures = [];
@@ -104458,8 +104469,7 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
104458
104469
  }, transcript, true, db2);
104459
104470
  const diagnostic = this.isPlatformRoutedDiagnosticMetadata(graded.metadata) ? summarizeDiagnosticSubmission(graded.metadata, graded.manifest) : null;
104460
104471
  const itemOutcomes = graded.metadata.purpose === "review" ? assessmentReviewTranscriptOutcomes({
104461
- metadata: graded.metadata,
104462
- itemResults: graded.itemResults
104472
+ metadata: graded.metadata
104463
104473
  }) : undefined;
104464
104474
  const courseName = attempt.metadata.courseName ?? "Game Course";
104465
104475
  const timestamp6 = new Date().toISOString();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@playcademy/sandbox",
3
- "version": "0.9.1-beta.1",
3
+ "version": "0.10.1-beta.1",
4
4
  "description": "Local development server for Playcademy game development",
5
5
  "type": "module",
6
6
  "exports": {