@playcademy/sandbox 0.5.1 → 0.5.2-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 +136 -26
  2. package/dist/server.js +136 -26
  3. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -1078,7 +1078,7 @@ var package_default;
1078
1078
  var init_package = __esm(() => {
1079
1079
  package_default = {
1080
1080
  name: "@playcademy/sandbox",
1081
- version: "0.5.1",
1081
+ version: "0.5.2-beta.1",
1082
1082
  description: "Local development server for Playcademy game development",
1083
1083
  type: "module",
1084
1084
  exports: {
@@ -31916,6 +31916,95 @@ class TimebackCacheManager {
31916
31916
  this.enrollmentCache.cleanup();
31917
31917
  }
31918
31918
  }
31919
+ function computeMasteryPct(masteredUnits, masterableUnits) {
31920
+ if (masterableUnits <= 0) {
31921
+ return 0;
31922
+ }
31923
+ return masteredUnits / masterableUnits;
31924
+ }
31925
+ function clampMasteryPct(masteryPct) {
31926
+ return Math.min(1, Math.max(0, masteryPct));
31927
+ }
31928
+ function createMasteryWriteWarning({
31929
+ currentMasteredUnits,
31930
+ attemptedMasteredUnits,
31931
+ storedMasteredUnits,
31932
+ masterableUnits,
31933
+ effectiveDelta
31934
+ }) {
31935
+ let message;
31936
+ if (attemptedMasteredUnits < 0) {
31937
+ message = `Activity saved. Mastery was capped at the configured minimum: attempted ${attemptedMasteredUnits}/${masterableUnits}, applied ${effectiveDelta} units, stored ${storedMasteredUnits}/${masterableUnits}.`;
31938
+ } else if (storedMasteredUnits > masterableUnits) {
31939
+ message = `Activity saved. Mastery remains above the configured maximum: attempted ${attemptedMasteredUnits}/${masterableUnits}, applied ${effectiveDelta} units, stored ${storedMasteredUnits}/${masterableUnits}.`;
31940
+ } else {
31941
+ message = `Activity saved. Mastery was capped at the configured maximum: attempted ${attemptedMasteredUnits}/${masterableUnits}, applied ${effectiveDelta} units, stored ${storedMasteredUnits}/${masterableUnits}.`;
31942
+ }
31943
+ return {
31944
+ code: MASTERY_WRITE_CAPPED_WARNING_CODE,
31945
+ message,
31946
+ currentMasteredUnits,
31947
+ attemptedMasteredUnits,
31948
+ appliedMasteredUnits: effectiveDelta,
31949
+ storedMasteredUnits,
31950
+ masterableUnits
31951
+ };
31952
+ }
31953
+ function evaluateMasteryBounds({
31954
+ currentMasteredUnits,
31955
+ masterableUnits,
31956
+ masteredUnits = 0,
31957
+ masteredUnitsAbsolute
31958
+ }) {
31959
+ const attemptedMasteredUnits = masteredUnitsAbsolute === undefined ? currentMasteredUnits + masteredUnits : masteredUnitsAbsolute;
31960
+ const base = {
31961
+ currentMasteredUnits,
31962
+ attemptedMasteredUnits,
31963
+ masterableUnits
31964
+ };
31965
+ if (!Number.isFinite(currentMasteredUnits) || !Number.isFinite(masterableUnits) || !Number.isFinite(masteredUnits) || masteredUnitsAbsolute !== undefined && !Number.isFinite(masteredUnitsAbsolute) || !Number.isFinite(attemptedMasteredUnits)) {
31966
+ throw new Error("Mastery bounds evaluation requires finite numeric values.");
31967
+ }
31968
+ if (attemptedMasteredUnits < 0) {
31969
+ const storedMasteredUnits2 = 0;
31970
+ const effectiveDelta2 = storedMasteredUnits2 - currentMasteredUnits;
31971
+ return {
31972
+ ...base,
31973
+ storedMasteredUnits: storedMasteredUnits2,
31974
+ effectiveDelta: effectiveDelta2,
31975
+ writeWarning: createMasteryWriteWarning({
31976
+ currentMasteredUnits,
31977
+ attemptedMasteredUnits,
31978
+ storedMasteredUnits: storedMasteredUnits2,
31979
+ masterableUnits,
31980
+ effectiveDelta: effectiveDelta2
31981
+ })
31982
+ };
31983
+ }
31984
+ if (masterableUnits > 0 && attemptedMasteredUnits > masterableUnits) {
31985
+ const storedMasteredUnits2 = currentMasteredUnits > masterableUnits ? Math.min(currentMasteredUnits, attemptedMasteredUnits) : masterableUnits;
31986
+ const effectiveDelta2 = storedMasteredUnits2 - currentMasteredUnits;
31987
+ return {
31988
+ ...base,
31989
+ storedMasteredUnits: storedMasteredUnits2,
31990
+ effectiveDelta: effectiveDelta2,
31991
+ writeWarning: createMasteryWriteWarning({
31992
+ currentMasteredUnits,
31993
+ attemptedMasteredUnits,
31994
+ storedMasteredUnits: storedMasteredUnits2,
31995
+ masterableUnits,
31996
+ effectiveDelta: effectiveDelta2
31997
+ })
31998
+ };
31999
+ }
32000
+ const storedMasteredUnits = attemptedMasteredUnits;
32001
+ const effectiveDelta = storedMasteredUnits - currentMasteredUnits;
32002
+ return {
32003
+ ...base,
32004
+ storedMasteredUnits,
32005
+ effectiveDelta
32006
+ };
32007
+ }
31919
32008
 
31920
32009
  class MasteryTracker {
31921
32010
  cacheManager;
@@ -31938,7 +32027,8 @@ class MasteryTracker {
31938
32027
  courseId,
31939
32028
  resourceId,
31940
32029
  additionalMasteredUnits: hasAbsolute ? 0 : masteredUnits,
31941
- absoluteMasteredUnits: hasAbsolute ? masteredUnitsAbsolute : undefined
32030
+ absoluteMasteredUnits: hasAbsolute ? masteredUnitsAbsolute : undefined,
32031
+ validateBounds: true
31942
32032
  });
31943
32033
  if (!status) {
31944
32034
  return;
@@ -31948,13 +32038,15 @@ class MasteryTracker {
31948
32038
  pctCompleteApp: status.pctCompleteApp,
31949
32039
  masteryAchieved: !wasComplete && status.isComplete,
31950
32040
  masteryRevoked: wasComplete && !status.isComplete,
31951
- effectiveDelta: status.effectiveDelta
32041
+ effectiveDelta: status.effectiveDelta,
32042
+ ...status.writeWarning ? { writeWarning: status.writeWarning } : {}
31952
32043
  };
31953
32044
  }
31954
32045
  async getStatus(input) {
31955
32046
  const status = await this.calculateStatus({
31956
32047
  ...input,
31957
- additionalMasteredUnits: 0
32048
+ additionalMasteredUnits: 0,
32049
+ validateBounds: false
31958
32050
  });
31959
32051
  if (!status) {
31960
32052
  return;
@@ -31971,7 +32063,8 @@ class MasteryTracker {
31971
32063
  courseId,
31972
32064
  resourceId,
31973
32065
  additionalMasteredUnits,
31974
- absoluteMasteredUnits
32066
+ absoluteMasteredUnits,
32067
+ validateBounds
31975
32068
  }) {
31976
32069
  const masterableUnits = await this.resolveMasterableUnits(resourceId);
31977
32070
  if (!masterableUnits || masterableUnits <= 0) {
@@ -31984,24 +32077,38 @@ class MasteryTracker {
31984
32077
  return;
31985
32078
  }
31986
32079
  const historicalMasteredUnits = this.sumAnalyticsMetric(facts, "masteredUnits");
31987
- let totalMastered;
31988
- let effectiveDelta;
31989
- if (absoluteMasteredUnits !== undefined) {
31990
- totalMastered = Math.max(0, absoluteMasteredUnits);
31991
- effectiveDelta = totalMastered - historicalMasteredUnits;
31992
- } else {
31993
- effectiveDelta = additionalMasteredUnits;
31994
- totalMastered = Math.max(0, historicalMasteredUnits + effectiveDelta);
32080
+ const bounds = validateBounds ? evaluateMasteryBounds({
32081
+ currentMasteredUnits: historicalMasteredUnits,
32082
+ masterableUnits,
32083
+ masteredUnits: additionalMasteredUnits,
32084
+ masteredUnitsAbsolute: absoluteMasteredUnits
32085
+ }) : {
32086
+ currentMasteredUnits: historicalMasteredUnits,
32087
+ attemptedMasteredUnits: historicalMasteredUnits,
32088
+ storedMasteredUnits: historicalMasteredUnits,
32089
+ masterableUnits,
32090
+ effectiveDelta: 0
32091
+ };
32092
+ if (validateBounds && bounds.writeWarning) {
32093
+ addEvent("timeback.mastery_write_capped", {
32094
+ "app.timeback.current_mastered_units": bounds.currentMasteredUnits,
32095
+ "app.timeback.attempted_mastered_units": bounds.attemptedMasteredUnits,
32096
+ "app.timeback.applied_mastered_units": bounds.effectiveDelta,
32097
+ "app.timeback.stored_mastered_units": bounds.storedMasteredUnits,
32098
+ "app.timeback.masterable_units": bounds.masterableUnits
32099
+ });
31995
32100
  }
31996
- const rawPct = totalMastered / masterableUnits * 100;
31997
- const pctCompleteApp = Math.min(100, Math.max(0, Math.round(rawPct)));
32101
+ const masteredUnits = bounds.storedMasteredUnits;
32102
+ const masteryPct = computeMasteryPct(masteredUnits, masterableUnits);
32103
+ const pctCompleteApp = Math.round(clampMasteryPct(masteryPct) * 100);
31998
32104
  return {
31999
- masteredUnits: totalMastered,
32105
+ masteredUnits,
32000
32106
  masterableUnits,
32001
32107
  pctCompleteApp,
32002
- isComplete: totalMastered >= masterableUnits,
32108
+ isComplete: masteredUnits >= masterableUnits,
32003
32109
  historicalMasteredUnits,
32004
- effectiveDelta
32110
+ effectiveDelta: bounds.effectiveDelta,
32111
+ ...bounds.writeWarning ? { writeWarning: bounds.writeWarning } : {}
32005
32112
  };
32006
32113
  }
32007
32114
  async createCompletionEntry(studentId, courseId, classId, appName) {
@@ -32200,8 +32307,6 @@ class ProgressRecorder {
32200
32307
  xpEarned = 0,
32201
32308
  attemptNumber
32202
32309
  } = progressData;
32203
- const actualLineItemId = await this.resolveAssessmentLineItem(activityId, activityName, progressData.classId, ids);
32204
- const currentAttemptNumber = await this.resolveAttemptNumber(attemptNumber, score, studentId, actualLineItemId);
32205
32310
  let extensions = progressData.extensions;
32206
32311
  const masteryProgress = await this.masteryTracker.checkProgress({
32207
32312
  studentId,
@@ -32215,6 +32320,7 @@ class ProgressRecorder {
32215
32320
  let masteryAchieved = false;
32216
32321
  let scoreStatus = SCORE_STATUS.fullyGraded;
32217
32322
  const inProgress = "false";
32323
+ const warnings = masteryProgress?.writeWarning ? [masteryProgress.writeWarning] : undefined;
32218
32324
  if (masteryProgress) {
32219
32325
  masteryAchieved = masteryProgress.masteryAchieved;
32220
32326
  pctCompleteApp = masteryProgress.pctCompleteApp;
@@ -32226,6 +32332,8 @@ class ProgressRecorder {
32226
32332
  scoreStatus = SCORE_STATUS.fullyGraded;
32227
32333
  }
32228
32334
  }
32335
+ const actualLineItemId = await this.resolveAssessmentLineItem(activityId, activityName, progressData.classId, ids);
32336
+ const currentAttemptNumber = await this.resolveAttemptNumber(attemptNumber, score, studentId, actualLineItemId);
32229
32337
  if (score !== undefined) {
32230
32338
  await this.createGradebookEntry({
32231
32339
  lineItemId: actualLineItemId,
@@ -32284,7 +32392,8 @@ class ProgressRecorder {
32284
32392
  masteredUnitsApplied: effectiveMasteredUnits,
32285
32393
  pctCompleteApp,
32286
32394
  scoreStatus,
32287
- inProgress
32395
+ inProgress,
32396
+ ...warnings ? { warnings } : {}
32288
32397
  };
32289
32398
  }
32290
32399
  async resolveContext(courseId, studentIdentifier, progressData) {
@@ -32970,7 +33079,7 @@ var __defProp2, __export2 = (target, all) => {
32970
33079
  configurable: true,
32971
33080
  set: (newValue) => all[name3] = () => newValue
32972
33081
  });
32973
- }, __esm2 = (fn, res) => () => (fn && (res = fn(fn = 0)), res), TIMEBACK_API_URLS, QTI_API_URL = "https://qti.alpha-1edtech.ai/api", TIMEBACK_AUTH_URLS, CALIPER_API_URLS, ONEROSTER_ENDPOINTS, QTI_ENDPOINTS, CALIPER_ENDPOINTS, CALIPER_CONSTANTS, TIMEBACK_EVENT_TYPES, TIMEBACK_ACTIONS, TIMEBACK_TYPES, ACTIVITY_METRIC_TYPES, TIME_METRIC_TYPES, TIMEBACK_SUBJECTS, TIMEBACK_GRADE_LEVELS, TIMEBACK_GRADE_LEVEL_LABELS, CALIPER_SUBJECTS, ONEROSTER_STATUS, SCORE_STATUS, ENV_VARS, HTTP_DEFAULTS, AUTH_DEFAULTS, CACHE_DEFAULTS, CONFIG_DEFAULTS, PLAYCADEMY_DEFAULTS, RESOURCE_DEFAULTS, HTTP_STATUS, ERROR_NAMES, init_constants4, exports_verify, init_verify, TimebackError, TimebackApiError, TimebackAuthenticationError, StudentNotFoundError, ConfigurationError, ResourceNotFoundError, SUBJECT_VALUES, GRADE_VALUES, TimebackAuthError, UUID_PATTERN, storage, EmailSchema, StudentSourcedIdSchema, StudentIdentifierSchema;
33082
+ }, __esm2 = (fn, res) => () => (fn && (res = fn(fn = 0)), res), TIMEBACK_API_URLS, QTI_API_URL = "https://qti.alpha-1edtech.ai/api", TIMEBACK_AUTH_URLS, CALIPER_API_URLS, ONEROSTER_ENDPOINTS, QTI_ENDPOINTS, CALIPER_ENDPOINTS, CALIPER_CONSTANTS, TIMEBACK_EVENT_TYPES, TIMEBACK_ACTIONS, TIMEBACK_TYPES, ACTIVITY_METRIC_TYPES, TIME_METRIC_TYPES, TIMEBACK_SUBJECTS, TIMEBACK_GRADE_LEVELS, TIMEBACK_GRADE_LEVEL_LABELS, CALIPER_SUBJECTS, ONEROSTER_STATUS, SCORE_STATUS, ENV_VARS, HTTP_DEFAULTS, AUTH_DEFAULTS, CACHE_DEFAULTS, CONFIG_DEFAULTS, PLAYCADEMY_DEFAULTS, RESOURCE_DEFAULTS, HTTP_STATUS, ERROR_NAMES, init_constants4, exports_verify, init_verify, TimebackError, TimebackApiError, TimebackAuthenticationError, StudentNotFoundError, ConfigurationError, ResourceNotFoundError, SUBJECT_VALUES, GRADE_VALUES, TimebackAuthError, UUID_PATTERN, storage, MASTERY_WRITE_CAPPED_WARNING_CODE = "MASTERY_WRITE_CAPPED", EmailSchema, StudentSourcedIdSchema, StudentIdentifierSchema;
32974
33083
  var init_dist2 = __esm(() => {
32975
33084
  init_src();
32976
33085
  init_src();
@@ -34503,8 +34612,8 @@ var init_schemas4 = __esm(() => {
34503
34612
  inactiveSeconds: exports_external.number().nonnegative().optional()
34504
34613
  }).optional(),
34505
34614
  xpEarned: exports_external.number(),
34506
- masteredUnits: exports_external.number().optional(),
34507
- masteredUnitsAbsolute: exports_external.number().int().nonnegative().optional(),
34615
+ masteredUnits: exports_external.number().finite().optional(),
34616
+ masteredUnitsAbsolute: exports_external.number().finite().int().optional(),
34508
34617
  extensions: exports_external.record(exports_external.string(), exports_external.unknown()).optional()
34509
34618
  }).refine((data) => !(data.masteredUnits !== undefined && data.masteredUnitsAbsolute !== undefined), {
34510
34619
  message: "Cannot provide both masteredUnits and masteredUnitsAbsolute",
@@ -38605,7 +38714,8 @@ var init_timeback_service = __esm(() => {
38605
38714
  masteredUnits: result.masteredUnitsApplied,
38606
38715
  pctCompleteApp: result.pctCompleteApp,
38607
38716
  scoreStatus: result.scoreStatus,
38608
- inProgress: result.inProgress
38717
+ inProgress: result.inProgress,
38718
+ ...result.warnings ? { warnings: result.warnings } : {}
38609
38719
  };
38610
38720
  }
38611
38721
  async resolveActiveGameCourse({
@@ -98356,7 +98466,7 @@ var init_timeback7 = __esm(() => {
98356
98466
  const seed3 = hashCode(`${e.grade}-${e.subject}`);
98357
98467
  const masterableUnits = 5 + seed3 % 16;
98358
98468
  const masteredUnits = seed3 % (masterableUnits + 1);
98359
- const pctComplete = masterableUnits > 0 ? Math.round(masteredUnits / masterableUnits * 1e4) / 100 : 0;
98469
+ const pctComplete = masterableUnits > 0 ? Math.round(masteredUnits / masterableUnits * 100) : 0;
98360
98470
  return {
98361
98471
  grade: e.grade,
98362
98472
  subject: e.subject,
package/dist/server.js CHANGED
@@ -1077,7 +1077,7 @@ var package_default;
1077
1077
  var init_package = __esm(() => {
1078
1078
  package_default = {
1079
1079
  name: "@playcademy/sandbox",
1080
- version: "0.5.1",
1080
+ version: "0.5.2-beta.1",
1081
1081
  description: "Local development server for Playcademy game development",
1082
1082
  type: "module",
1083
1083
  exports: {
@@ -31915,6 +31915,95 @@ class TimebackCacheManager {
31915
31915
  this.enrollmentCache.cleanup();
31916
31916
  }
31917
31917
  }
31918
+ function computeMasteryPct(masteredUnits, masterableUnits) {
31919
+ if (masterableUnits <= 0) {
31920
+ return 0;
31921
+ }
31922
+ return masteredUnits / masterableUnits;
31923
+ }
31924
+ function clampMasteryPct(masteryPct) {
31925
+ return Math.min(1, Math.max(0, masteryPct));
31926
+ }
31927
+ function createMasteryWriteWarning({
31928
+ currentMasteredUnits,
31929
+ attemptedMasteredUnits,
31930
+ storedMasteredUnits,
31931
+ masterableUnits,
31932
+ effectiveDelta
31933
+ }) {
31934
+ let message;
31935
+ if (attemptedMasteredUnits < 0) {
31936
+ message = `Activity saved. Mastery was capped at the configured minimum: attempted ${attemptedMasteredUnits}/${masterableUnits}, applied ${effectiveDelta} units, stored ${storedMasteredUnits}/${masterableUnits}.`;
31937
+ } else if (storedMasteredUnits > masterableUnits) {
31938
+ message = `Activity saved. Mastery remains above the configured maximum: attempted ${attemptedMasteredUnits}/${masterableUnits}, applied ${effectiveDelta} units, stored ${storedMasteredUnits}/${masterableUnits}.`;
31939
+ } else {
31940
+ message = `Activity saved. Mastery was capped at the configured maximum: attempted ${attemptedMasteredUnits}/${masterableUnits}, applied ${effectiveDelta} units, stored ${storedMasteredUnits}/${masterableUnits}.`;
31941
+ }
31942
+ return {
31943
+ code: MASTERY_WRITE_CAPPED_WARNING_CODE,
31944
+ message,
31945
+ currentMasteredUnits,
31946
+ attemptedMasteredUnits,
31947
+ appliedMasteredUnits: effectiveDelta,
31948
+ storedMasteredUnits,
31949
+ masterableUnits
31950
+ };
31951
+ }
31952
+ function evaluateMasteryBounds({
31953
+ currentMasteredUnits,
31954
+ masterableUnits,
31955
+ masteredUnits = 0,
31956
+ masteredUnitsAbsolute
31957
+ }) {
31958
+ const attemptedMasteredUnits = masteredUnitsAbsolute === undefined ? currentMasteredUnits + masteredUnits : masteredUnitsAbsolute;
31959
+ const base = {
31960
+ currentMasteredUnits,
31961
+ attemptedMasteredUnits,
31962
+ masterableUnits
31963
+ };
31964
+ if (!Number.isFinite(currentMasteredUnits) || !Number.isFinite(masterableUnits) || !Number.isFinite(masteredUnits) || masteredUnitsAbsolute !== undefined && !Number.isFinite(masteredUnitsAbsolute) || !Number.isFinite(attemptedMasteredUnits)) {
31965
+ throw new Error("Mastery bounds evaluation requires finite numeric values.");
31966
+ }
31967
+ if (attemptedMasteredUnits < 0) {
31968
+ const storedMasteredUnits2 = 0;
31969
+ const effectiveDelta2 = storedMasteredUnits2 - currentMasteredUnits;
31970
+ return {
31971
+ ...base,
31972
+ storedMasteredUnits: storedMasteredUnits2,
31973
+ effectiveDelta: effectiveDelta2,
31974
+ writeWarning: createMasteryWriteWarning({
31975
+ currentMasteredUnits,
31976
+ attemptedMasteredUnits,
31977
+ storedMasteredUnits: storedMasteredUnits2,
31978
+ masterableUnits,
31979
+ effectiveDelta: effectiveDelta2
31980
+ })
31981
+ };
31982
+ }
31983
+ if (masterableUnits > 0 && attemptedMasteredUnits > masterableUnits) {
31984
+ const storedMasteredUnits2 = currentMasteredUnits > masterableUnits ? Math.min(currentMasteredUnits, attemptedMasteredUnits) : masterableUnits;
31985
+ const effectiveDelta2 = storedMasteredUnits2 - currentMasteredUnits;
31986
+ return {
31987
+ ...base,
31988
+ storedMasteredUnits: storedMasteredUnits2,
31989
+ effectiveDelta: effectiveDelta2,
31990
+ writeWarning: createMasteryWriteWarning({
31991
+ currentMasteredUnits,
31992
+ attemptedMasteredUnits,
31993
+ storedMasteredUnits: storedMasteredUnits2,
31994
+ masterableUnits,
31995
+ effectiveDelta: effectiveDelta2
31996
+ })
31997
+ };
31998
+ }
31999
+ const storedMasteredUnits = attemptedMasteredUnits;
32000
+ const effectiveDelta = storedMasteredUnits - currentMasteredUnits;
32001
+ return {
32002
+ ...base,
32003
+ storedMasteredUnits,
32004
+ effectiveDelta
32005
+ };
32006
+ }
31918
32007
 
31919
32008
  class MasteryTracker {
31920
32009
  cacheManager;
@@ -31937,7 +32026,8 @@ class MasteryTracker {
31937
32026
  courseId,
31938
32027
  resourceId,
31939
32028
  additionalMasteredUnits: hasAbsolute ? 0 : masteredUnits,
31940
- absoluteMasteredUnits: hasAbsolute ? masteredUnitsAbsolute : undefined
32029
+ absoluteMasteredUnits: hasAbsolute ? masteredUnitsAbsolute : undefined,
32030
+ validateBounds: true
31941
32031
  });
31942
32032
  if (!status) {
31943
32033
  return;
@@ -31947,13 +32037,15 @@ class MasteryTracker {
31947
32037
  pctCompleteApp: status.pctCompleteApp,
31948
32038
  masteryAchieved: !wasComplete && status.isComplete,
31949
32039
  masteryRevoked: wasComplete && !status.isComplete,
31950
- effectiveDelta: status.effectiveDelta
32040
+ effectiveDelta: status.effectiveDelta,
32041
+ ...status.writeWarning ? { writeWarning: status.writeWarning } : {}
31951
32042
  };
31952
32043
  }
31953
32044
  async getStatus(input) {
31954
32045
  const status = await this.calculateStatus({
31955
32046
  ...input,
31956
- additionalMasteredUnits: 0
32047
+ additionalMasteredUnits: 0,
32048
+ validateBounds: false
31957
32049
  });
31958
32050
  if (!status) {
31959
32051
  return;
@@ -31970,7 +32062,8 @@ class MasteryTracker {
31970
32062
  courseId,
31971
32063
  resourceId,
31972
32064
  additionalMasteredUnits,
31973
- absoluteMasteredUnits
32065
+ absoluteMasteredUnits,
32066
+ validateBounds
31974
32067
  }) {
31975
32068
  const masterableUnits = await this.resolveMasterableUnits(resourceId);
31976
32069
  if (!masterableUnits || masterableUnits <= 0) {
@@ -31983,24 +32076,38 @@ class MasteryTracker {
31983
32076
  return;
31984
32077
  }
31985
32078
  const historicalMasteredUnits = this.sumAnalyticsMetric(facts, "masteredUnits");
31986
- let totalMastered;
31987
- let effectiveDelta;
31988
- if (absoluteMasteredUnits !== undefined) {
31989
- totalMastered = Math.max(0, absoluteMasteredUnits);
31990
- effectiveDelta = totalMastered - historicalMasteredUnits;
31991
- } else {
31992
- effectiveDelta = additionalMasteredUnits;
31993
- totalMastered = Math.max(0, historicalMasteredUnits + effectiveDelta);
32079
+ const bounds = validateBounds ? evaluateMasteryBounds({
32080
+ currentMasteredUnits: historicalMasteredUnits,
32081
+ masterableUnits,
32082
+ masteredUnits: additionalMasteredUnits,
32083
+ masteredUnitsAbsolute: absoluteMasteredUnits
32084
+ }) : {
32085
+ currentMasteredUnits: historicalMasteredUnits,
32086
+ attemptedMasteredUnits: historicalMasteredUnits,
32087
+ storedMasteredUnits: historicalMasteredUnits,
32088
+ masterableUnits,
32089
+ effectiveDelta: 0
32090
+ };
32091
+ if (validateBounds && bounds.writeWarning) {
32092
+ addEvent("timeback.mastery_write_capped", {
32093
+ "app.timeback.current_mastered_units": bounds.currentMasteredUnits,
32094
+ "app.timeback.attempted_mastered_units": bounds.attemptedMasteredUnits,
32095
+ "app.timeback.applied_mastered_units": bounds.effectiveDelta,
32096
+ "app.timeback.stored_mastered_units": bounds.storedMasteredUnits,
32097
+ "app.timeback.masterable_units": bounds.masterableUnits
32098
+ });
31994
32099
  }
31995
- const rawPct = totalMastered / masterableUnits * 100;
31996
- const pctCompleteApp = Math.min(100, Math.max(0, Math.round(rawPct)));
32100
+ const masteredUnits = bounds.storedMasteredUnits;
32101
+ const masteryPct = computeMasteryPct(masteredUnits, masterableUnits);
32102
+ const pctCompleteApp = Math.round(clampMasteryPct(masteryPct) * 100);
31997
32103
  return {
31998
- masteredUnits: totalMastered,
32104
+ masteredUnits,
31999
32105
  masterableUnits,
32000
32106
  pctCompleteApp,
32001
- isComplete: totalMastered >= masterableUnits,
32107
+ isComplete: masteredUnits >= masterableUnits,
32002
32108
  historicalMasteredUnits,
32003
- effectiveDelta
32109
+ effectiveDelta: bounds.effectiveDelta,
32110
+ ...bounds.writeWarning ? { writeWarning: bounds.writeWarning } : {}
32004
32111
  };
32005
32112
  }
32006
32113
  async createCompletionEntry(studentId, courseId, classId, appName) {
@@ -32199,8 +32306,6 @@ class ProgressRecorder {
32199
32306
  xpEarned = 0,
32200
32307
  attemptNumber
32201
32308
  } = progressData;
32202
- const actualLineItemId = await this.resolveAssessmentLineItem(activityId, activityName, progressData.classId, ids);
32203
- const currentAttemptNumber = await this.resolveAttemptNumber(attemptNumber, score, studentId, actualLineItemId);
32204
32309
  let extensions = progressData.extensions;
32205
32310
  const masteryProgress = await this.masteryTracker.checkProgress({
32206
32311
  studentId,
@@ -32214,6 +32319,7 @@ class ProgressRecorder {
32214
32319
  let masteryAchieved = false;
32215
32320
  let scoreStatus = SCORE_STATUS.fullyGraded;
32216
32321
  const inProgress = "false";
32322
+ const warnings = masteryProgress?.writeWarning ? [masteryProgress.writeWarning] : undefined;
32217
32323
  if (masteryProgress) {
32218
32324
  masteryAchieved = masteryProgress.masteryAchieved;
32219
32325
  pctCompleteApp = masteryProgress.pctCompleteApp;
@@ -32225,6 +32331,8 @@ class ProgressRecorder {
32225
32331
  scoreStatus = SCORE_STATUS.fullyGraded;
32226
32332
  }
32227
32333
  }
32334
+ const actualLineItemId = await this.resolveAssessmentLineItem(activityId, activityName, progressData.classId, ids);
32335
+ const currentAttemptNumber = await this.resolveAttemptNumber(attemptNumber, score, studentId, actualLineItemId);
32228
32336
  if (score !== undefined) {
32229
32337
  await this.createGradebookEntry({
32230
32338
  lineItemId: actualLineItemId,
@@ -32283,7 +32391,8 @@ class ProgressRecorder {
32283
32391
  masteredUnitsApplied: effectiveMasteredUnits,
32284
32392
  pctCompleteApp,
32285
32393
  scoreStatus,
32286
- inProgress
32394
+ inProgress,
32395
+ ...warnings ? { warnings } : {}
32287
32396
  };
32288
32397
  }
32289
32398
  async resolveContext(courseId, studentIdentifier, progressData) {
@@ -32969,7 +33078,7 @@ var __defProp2, __export2 = (target, all) => {
32969
33078
  configurable: true,
32970
33079
  set: (newValue) => all[name3] = () => newValue
32971
33080
  });
32972
- }, __esm2 = (fn, res) => () => (fn && (res = fn(fn = 0)), res), TIMEBACK_API_URLS, QTI_API_URL = "https://qti.alpha-1edtech.ai/api", TIMEBACK_AUTH_URLS, CALIPER_API_URLS, ONEROSTER_ENDPOINTS, QTI_ENDPOINTS, CALIPER_ENDPOINTS, CALIPER_CONSTANTS, TIMEBACK_EVENT_TYPES, TIMEBACK_ACTIONS, TIMEBACK_TYPES, ACTIVITY_METRIC_TYPES, TIME_METRIC_TYPES, TIMEBACK_SUBJECTS, TIMEBACK_GRADE_LEVELS, TIMEBACK_GRADE_LEVEL_LABELS, CALIPER_SUBJECTS, ONEROSTER_STATUS, SCORE_STATUS, ENV_VARS, HTTP_DEFAULTS, AUTH_DEFAULTS, CACHE_DEFAULTS, CONFIG_DEFAULTS, PLAYCADEMY_DEFAULTS, RESOURCE_DEFAULTS, HTTP_STATUS, ERROR_NAMES, init_constants4, exports_verify, init_verify, TimebackError, TimebackApiError, TimebackAuthenticationError, StudentNotFoundError, ConfigurationError, ResourceNotFoundError, SUBJECT_VALUES, GRADE_VALUES, TimebackAuthError, UUID_PATTERN, storage, EmailSchema, StudentSourcedIdSchema, StudentIdentifierSchema;
33081
+ }, __esm2 = (fn, res) => () => (fn && (res = fn(fn = 0)), res), TIMEBACK_API_URLS, QTI_API_URL = "https://qti.alpha-1edtech.ai/api", TIMEBACK_AUTH_URLS, CALIPER_API_URLS, ONEROSTER_ENDPOINTS, QTI_ENDPOINTS, CALIPER_ENDPOINTS, CALIPER_CONSTANTS, TIMEBACK_EVENT_TYPES, TIMEBACK_ACTIONS, TIMEBACK_TYPES, ACTIVITY_METRIC_TYPES, TIME_METRIC_TYPES, TIMEBACK_SUBJECTS, TIMEBACK_GRADE_LEVELS, TIMEBACK_GRADE_LEVEL_LABELS, CALIPER_SUBJECTS, ONEROSTER_STATUS, SCORE_STATUS, ENV_VARS, HTTP_DEFAULTS, AUTH_DEFAULTS, CACHE_DEFAULTS, CONFIG_DEFAULTS, PLAYCADEMY_DEFAULTS, RESOURCE_DEFAULTS, HTTP_STATUS, ERROR_NAMES, init_constants4, exports_verify, init_verify, TimebackError, TimebackApiError, TimebackAuthenticationError, StudentNotFoundError, ConfigurationError, ResourceNotFoundError, SUBJECT_VALUES, GRADE_VALUES, TimebackAuthError, UUID_PATTERN, storage, MASTERY_WRITE_CAPPED_WARNING_CODE = "MASTERY_WRITE_CAPPED", EmailSchema, StudentSourcedIdSchema, StudentIdentifierSchema;
32973
33082
  var init_dist2 = __esm(() => {
32974
33083
  init_src();
32975
33084
  init_src();
@@ -34502,8 +34611,8 @@ var init_schemas4 = __esm(() => {
34502
34611
  inactiveSeconds: exports_external.number().nonnegative().optional()
34503
34612
  }).optional(),
34504
34613
  xpEarned: exports_external.number(),
34505
- masteredUnits: exports_external.number().optional(),
34506
- masteredUnitsAbsolute: exports_external.number().int().nonnegative().optional(),
34614
+ masteredUnits: exports_external.number().finite().optional(),
34615
+ masteredUnitsAbsolute: exports_external.number().finite().int().optional(),
34507
34616
  extensions: exports_external.record(exports_external.string(), exports_external.unknown()).optional()
34508
34617
  }).refine((data) => !(data.masteredUnits !== undefined && data.masteredUnitsAbsolute !== undefined), {
34509
34618
  message: "Cannot provide both masteredUnits and masteredUnitsAbsolute",
@@ -38604,7 +38713,8 @@ var init_timeback_service = __esm(() => {
38604
38713
  masteredUnits: result.masteredUnitsApplied,
38605
38714
  pctCompleteApp: result.pctCompleteApp,
38606
38715
  scoreStatus: result.scoreStatus,
38607
- inProgress: result.inProgress
38716
+ inProgress: result.inProgress,
38717
+ ...result.warnings ? { warnings: result.warnings } : {}
38608
38718
  };
38609
38719
  }
38610
38720
  async resolveActiveGameCourse({
@@ -98355,7 +98465,7 @@ var init_timeback7 = __esm(() => {
98355
98465
  const seed3 = hashCode(`${e.grade}-${e.subject}`);
98356
98466
  const masterableUnits = 5 + seed3 % 16;
98357
98467
  const masteredUnits = seed3 % (masterableUnits + 1);
98358
- const pctComplete = masterableUnits > 0 ? Math.round(masteredUnits / masterableUnits * 1e4) / 100 : 0;
98468
+ const pctComplete = masterableUnits > 0 ? Math.round(masteredUnits / masterableUnits * 100) : 0;
98359
98469
  return {
98360
98470
  grade: e.grade,
98361
98471
  subject: e.subject,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@playcademy/sandbox",
3
- "version": "0.5.1",
3
+ "version": "0.5.2-beta.1",
4
4
  "description": "Local development server for Playcademy game development",
5
5
  "type": "module",
6
6
  "exports": {