@playcademy/vite-plugin 0.2.30-beta.1 → 0.2.30-beta.3

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 +95 -153
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -25370,7 +25370,7 @@ var package_default;
25370
25370
  var init_package = __esm(() => {
25371
25371
  package_default = {
25372
25372
  name: "@playcademy/sandbox",
25373
- version: "0.3.17-beta.34",
25373
+ version: "0.3.17-beta.36",
25374
25374
  description: "Local development server for Playcademy game development",
25375
25375
  type: "module",
25376
25376
  exports: {
@@ -54340,6 +54340,24 @@ function resolveAdminEventTime(data) {
54340
54340
  }
54341
54341
  return toAttributionEventTime(data.date);
54342
54342
  }
54343
+ function validateMasteryAdjustment(delta, currentMastered, masterableUnits) {
54344
+ if (delta < 0 && currentMastered + delta < 0) {
54345
+ throw new ValidationError(`Adjustment would go below 0. Current: ${currentMastered}, adjustment: ${delta}`);
54346
+ }
54347
+ if (delta < 0 && typeof masterableUnits === "number" && masterableUnits > 0 && currentMastered > masterableUnits && currentMastered + delta > masterableUnits) {
54348
+ const minDelta = masterableUnits - currentMastered;
54349
+ throw new ValidationError(`Adjustment must reduce mastery to at most ${masterableUnits}. Current: ${currentMastered}/${masterableUnits}, minimum adjustment: ${minDelta}`);
54350
+ }
54351
+ if (delta > 0 && typeof masterableUnits === "number" && masterableUnits > 0) {
54352
+ if (currentMastered >= masterableUnits) {
54353
+ throw new ValidationError(`Mastery is already at maximum (${currentMastered}/${masterableUnits}). Only negative adjustments are allowed.`);
54354
+ }
54355
+ if (currentMastered + delta > masterableUnits) {
54356
+ const remaining = masterableUnits - currentMastered;
54357
+ throw new ValidationError(`Adjustment would exceed maximum. Current: ${currentMastered}/${masterableUnits}, max adjustment: +${remaining}`);
54358
+ }
54359
+ }
54360
+ }
54343
54361
  function compareEnrollmentsByRecency(a, b) {
54344
54362
  const dateCompare = (b.beginDate ?? "").localeCompare(a.beginDate ?? "");
54345
54363
  if (dateCompare !== 0) {
@@ -54696,17 +54714,6 @@ class TimebackAdminService {
54696
54714
  }
54697
54715
  return this.deps.timeback;
54698
54716
  }
54699
- async recordCourseCompletionHistory(client, data) {
54700
- await client.recordAdminCourseCompletionChange(data).catch((error) => {
54701
- logger16.error("Failed to record admin course completion history event", {
54702
- gameId: data.gameId,
54703
- courseId: data.courseId,
54704
- studentId: data.studentId,
54705
- action: data.action,
54706
- error: error instanceof Error ? error.message : String(error)
54707
- });
54708
- });
54709
- }
54710
54717
  async resolveAdminMutationContext(gameId, courseId, user, studentId, accessLevel = "developer") {
54711
54718
  const client = this.requireClient();
54712
54719
  if (accessLevel === "dashboard") {
@@ -54805,7 +54812,7 @@ class TimebackAdminService {
54805
54812
  if (typeof masterableUnits !== "number" || masterableUnits <= 0) {
54806
54813
  return;
54807
54814
  }
54808
- return Math.min(100, Math.round(masteredUnits / masterableUnits * 100));
54815
+ return Math.round(masteredUnits / masterableUnits * 100);
54809
54816
  }
54810
54817
  async getMasterableUnits(courseId) {
54811
54818
  const client = this.requireClient();
@@ -55081,6 +55088,7 @@ class TimebackAdminService {
55081
55088
  ...enrollmentSummaries ? { enrollments: enrollmentSummaries } : {}
55082
55089
  };
55083
55090
  });
55091
+ courses.sort((a, b) => a.grade - b.grade);
55084
55092
  return {
55085
55093
  student: {
55086
55094
  studentId,
@@ -55180,111 +55188,87 @@ class TimebackAdminService {
55180
55188
  return { status: "ok" };
55181
55189
  }
55182
55190
  async adjustMasteredUnits(data, user) {
55183
- const { client, sensorUrl, appName, actor } = await this.resolveAdminMutationContext(data.gameId, data.courseId, user, data.studentId);
55191
+ const { client, sensorUrl, appName, actor } = await this.resolveAdminMutationContext(data.gameId, data.courseId, user);
55192
+ let currentMastered = 0;
55193
+ const masterableUnits = await this.getMasterableUnits(data.courseId);
55194
+ if (data.units !== 0) {
55195
+ const enrollment = await this.assertStudentEnrolledInCourse(client, data.studentId, data.courseId);
55196
+ try {
55197
+ const analytics = await client.edubridge.analytics.getEnrollmentFacts(enrollment.id, { timezone: PLATFORM_TIMEZONE });
55198
+ currentMastered = this.summarizeAnalyticsFacts(analytics.facts).masteredUnits;
55199
+ } catch {
55200
+ throw new ValidationError("Unable to validate mastery bounds — analytics unavailable. Please retry.");
55201
+ }
55202
+ validateMasteryAdjustment(data.units, currentMastered, masterableUnits);
55203
+ }
55204
+ const pctCompleteApp = typeof masterableUnits === "number" && masterableUnits > 0 ? Math.min(100, Math.max(0, Math.round((currentMastered + data.units) / masterableUnits * 100))) : undefined;
55184
55205
  await client.recordAdminMasteryAdjustment({
55185
55206
  gameId: data.gameId,
55186
55207
  courseId: data.courseId,
55187
55208
  studentId: data.studentId,
55188
55209
  masteredUnits: data.units,
55210
+ pctCompleteApp,
55189
55211
  eventTime: resolveAdminEventTime(data),
55190
55212
  reason: data.reason,
55191
55213
  actor,
55192
55214
  appName,
55193
55215
  sensorUrl
55194
55216
  });
55195
- return { status: "ok" };
55196
- }
55197
- async toggleCourseCompletion(data, user) {
55198
- const { client, sensorUrl, appName, actor } = await this.resolveAdminMutationContext(data.gameId, data.courseId, user, data.studentId, "dashboard");
55199
- const historyClient = client;
55200
- const ids = deriveSourcedIds(data.courseId);
55201
- const lineItemId = `${ids.course}-mastery-completion-assessment`;
55202
- const resultId = `${lineItemId}:${data.studentId}:completion`;
55203
- await client.oneroster.assessmentLineItems.findOrCreate(lineItemId, {
55204
- sourcedId: lineItemId,
55205
- title: "Mastery Completion",
55206
- status: ONEROSTER_STATUS.active,
55207
- course: { sourcedId: ids.course },
55208
- ...ids.componentResource ? { componentResource: { sourcedId: ids.componentResource } } : {}
55209
- });
55210
- if (data.action === "complete") {
55211
- const masterableUnits = await this.getMasterableUnits(data.courseId);
55212
- if (masterableUnits && masterableUnits > 0) {
55213
- const enrollment = await this.assertStudentEnrolledInCourse(client, data.studentId, data.courseId);
55214
- let currentMastered = 0;
55215
- try {
55216
- const analytics = await client.edubridge.analytics.getEnrollmentFacts(enrollment.id, { timezone: PLATFORM_TIMEZONE });
55217
- const summary = this.summarizeAnalyticsFacts(analytics.facts);
55218
- currentMastered = summary.masteredUnits;
55219
- } catch {
55220
- logger16.warn("Failed to load analytics for mastery gap calculation", {
55221
- studentId: data.studentId,
55222
- courseId: data.courseId
55217
+ if (typeof masterableUnits === "number" && masterableUnits > 0) {
55218
+ const wasMastered = currentMastered >= masterableUnits;
55219
+ const willBeMastered = currentMastered + data.units >= masterableUnits;
55220
+ if (wasMastered !== willBeMastered) {
55221
+ const ids = deriveSourcedIds(data.courseId);
55222
+ const lineItemId = `${ids.course}-mastery-completion-assessment`;
55223
+ const resultId = `${lineItemId}:${data.studentId}:completion`;
55224
+ if (willBeMastered) {
55225
+ await client.oneroster.assessmentLineItems.findOrCreate(lineItemId, {
55226
+ sourcedId: lineItemId,
55227
+ title: "Mastery Completion",
55228
+ status: ONEROSTER_STATUS.active,
55229
+ course: { sourcedId: ids.course },
55230
+ ...ids.componentResource ? { componentResource: { sourcedId: ids.componentResource } } : {}
55223
55231
  });
55224
- }
55225
- const gap = masterableUnits - currentMastered;
55226
- if (gap > 0) {
55227
- await client.recordAdminMasteryAdjustment({
55228
- gameId: data.gameId,
55229
- courseId: data.courseId,
55230
- studentId: data.studentId,
55231
- masteredUnits: gap,
55232
- reason: "Admin completed course",
55233
- actor,
55234
- appName,
55235
- sensorUrl
55232
+ await client.oneroster.assessmentResults.upsert(resultId, {
55233
+ sourcedId: resultId,
55234
+ status: ONEROSTER_STATUS.active,
55235
+ assessmentLineItem: { sourcedId: lineItemId },
55236
+ student: { sourcedId: data.studentId },
55237
+ score: 100,
55238
+ scoreDate: new Date().toISOString(),
55239
+ scoreStatus: SCORE_STATUS.fullyGraded,
55240
+ inProgress: "false",
55241
+ metadata: {
55242
+ isMasteryCompletion: true,
55243
+ adminAction: true,
55244
+ appName
55245
+ }
55236
55246
  });
55247
+ } else {
55248
+ try {
55249
+ await client.oneroster.assessmentResults.upsert(resultId, {
55250
+ sourcedId: resultId,
55251
+ status: ONEROSTER_STATUS.active,
55252
+ assessmentLineItem: { sourcedId: lineItemId },
55253
+ student: { sourcedId: data.studentId },
55254
+ score: 0,
55255
+ scoreDate: new Date().toISOString(),
55256
+ scoreStatus: SCORE_STATUS.notSubmitted,
55257
+ inProgress: "true",
55258
+ metadata: {
55259
+ isMasteryCompletion: true,
55260
+ adminAction: true,
55261
+ appName
55262
+ }
55263
+ });
55264
+ } catch {
55265
+ logger16.debug("No completion entry to revoke", {
55266
+ studentId: data.studentId,
55267
+ courseId: data.courseId
55268
+ });
55269
+ }
55237
55270
  }
55238
55271
  }
55239
- await client.oneroster.assessmentResults.upsert(resultId, {
55240
- sourcedId: resultId,
55241
- status: ONEROSTER_STATUS.active,
55242
- assessmentLineItem: { sourcedId: lineItemId },
55243
- student: { sourcedId: data.studentId },
55244
- score: 100,
55245
- scoreDate: new Date().toISOString(),
55246
- scoreStatus: SCORE_STATUS.fullyGraded,
55247
- inProgress: "false",
55248
- metadata: {
55249
- isMasteryCompletion: true,
55250
- adminAction: true,
55251
- appName
55252
- }
55253
- });
55254
- await this.recordCourseCompletionHistory(historyClient, {
55255
- gameId: data.gameId,
55256
- courseId: data.courseId,
55257
- studentId: data.studentId,
55258
- action: "complete",
55259
- actor,
55260
- appName,
55261
- sensorUrl
55262
- });
55263
- } else {
55264
- await client.oneroster.assessmentResults.upsert(resultId, {
55265
- sourcedId: resultId,
55266
- status: ONEROSTER_STATUS.active,
55267
- assessmentLineItem: { sourcedId: lineItemId },
55268
- student: { sourcedId: data.studentId },
55269
- score: 0,
55270
- scoreDate: new Date().toISOString(),
55271
- scoreStatus: SCORE_STATUS.notSubmitted,
55272
- inProgress: "true",
55273
- metadata: {
55274
- isMasteryCompletion: true,
55275
- adminAction: true,
55276
- appName
55277
- }
55278
- });
55279
- await this.recordCourseCompletionHistory(historyClient, {
55280
- gameId: data.gameId,
55281
- courseId: data.courseId,
55282
- studentId: data.studentId,
55283
- action: "resume",
55284
- actor,
55285
- appName,
55286
- sensorUrl
55287
- });
55288
55272
  }
55289
55273
  return { status: "ok" };
55290
55274
  }
@@ -60927,6 +60911,7 @@ class AdminEventRecorder {
60927
60911
  defaultActivityId: "playcademy-admin-mastery-adjustment",
60928
60912
  eventKind: "remediation-mastery"
60929
60913
  });
60914
+ const courseUrl = createOneRosterUrls(TIMEBACK_API_URLS5[this.environment]).course(data.courseId);
60930
60915
  await this.caliper.emitActivityEvent({
60931
60916
  studentId: ctx.student.id,
60932
60917
  studentEmail: ctx.student.email,
@@ -60941,32 +60926,13 @@ class AdminEventRecorder {
60941
60926
  appName: ctx.appName,
60942
60927
  sensorUrl: ctx.sensorUrl,
60943
60928
  process: true,
60944
- generatedExtensions: ctx.metadata,
60945
- eventExtensions: ctx.metadata
60946
- });
60947
- }
60948
- async recordCourseCompletionChange(data) {
60949
- const isResume = data.action === "resume";
60950
- const ctx = await this.prepareAdminEvent({
60951
- ...data,
60952
- defaultActivityId: isResume ? "playcademy-admin-course-resumed" : "playcademy-admin-course-completed",
60953
- reason: "Admin action",
60954
- eventKind: isResume ? "course-resumed" : "course-completed"
60955
- });
60956
- await this.caliper.emitActivityEvent({
60957
- studentId: ctx.student.id,
60958
- studentEmail: ctx.student.email,
60959
- gameId: data.gameId,
60960
- activityId: ctx.activityId,
60961
- activityName: isResume ? "Course resumed" : "Course marked complete",
60962
- courseId: data.courseId,
60963
- courseName: ctx.courseContext.courseName,
60964
- subject: ctx.courseContext.subject,
60965
- appName: ctx.appName,
60966
- sensorUrl: ctx.sensorUrl,
60967
- process: false,
60968
60929
  includeAttempt: false,
60969
- generatedExtensions: ctx.metadata,
60930
+ objectId: `${ctx.sensorUrl.replace(/\/$/, "")}/urn:uuid:${crypto.randomUUID()}`,
60931
+ generatedId: courseUrl,
60932
+ generatedExtensions: {
60933
+ ...ctx.metadata,
60934
+ ...data.pctCompleteApp !== undefined ? { pctCompleteApp: data.pctCompleteApp } : {}
60935
+ },
60970
60936
  eventExtensions: ctx.metadata
60971
60937
  });
60972
60938
  }
@@ -62157,10 +62123,6 @@ class TimebackClient {
62157
62123
  await this._ensureAuthenticated();
62158
62124
  return this.adminEventRecorder.recordMasteryAdjustment(data);
62159
62125
  }
62160
- async recordAdminCourseCompletionChange(data) {
62161
- await this._ensureAuthenticated();
62162
- return this.adminEventRecorder.recordCourseCompletionChange(data);
62163
- }
62164
62126
  clearCaches() {
62165
62127
  this.cacheManager.clearAll();
62166
62128
  }
@@ -121794,7 +121756,6 @@ var AdminAttributionDateSchema;
121794
121756
  var GrantTimebackXpRequestSchema;
121795
121757
  var AdjustTimebackTimeRequestSchema;
121796
121758
  var AdjustTimebackMasteryRequestSchema;
121797
- var ToggleCourseCompletionRequestSchema;
121798
121759
  var EnrollStudentRequestSchema;
121799
121760
  var UnenrollStudentRequestSchema;
121800
121761
  var ReactivateEnrollmentRequestSchema;
@@ -121972,12 +121933,6 @@ var init_schemas11 = __esm(() => {
121972
121933
  date: AdminAttributionDateSchema.optional(),
121973
121934
  useCurrentTime: exports_external.boolean().optional()
121974
121935
  });
121975
- ToggleCourseCompletionRequestSchema = exports_external.object({
121976
- gameId: exports_external.string().uuid(),
121977
- courseId: exports_external.string().min(1),
121978
- studentId: exports_external.string().min(1),
121979
- action: exports_external.enum(["complete", "resume"])
121980
- });
121981
121936
  EnrollStudentRequestSchema = exports_external.object({
121982
121937
  gameId: exports_external.string().uuid(),
121983
121938
  courseId: exports_external.string().min(1),
@@ -124243,7 +124198,6 @@ var getActivityDetail;
124243
124198
  var grantXp;
124244
124199
  var adjustTime;
124245
124200
  var adjustMastery;
124246
- var toggleCompletion;
124247
124201
  var searchStudents;
124248
124202
  var enrollStudent;
124249
124203
  var unenrollStudent;
@@ -124662,17 +124616,6 @@ var init_timeback_controller = __esm(() => {
124662
124616
  });
124663
124617
  return ctx.services.timebackAdmin.adjustMasteredUnits(body2, ctx.user);
124664
124618
  });
124665
- toggleCompletion = requireGameManagementAccess(async (ctx) => {
124666
- const body2 = await parseRequestBody(ctx.request, ToggleCourseCompletionRequestSchema);
124667
- logger65.debug("Toggling course completion", {
124668
- requesterId: ctx.user.id,
124669
- gameId: body2.gameId,
124670
- courseId: body2.courseId,
124671
- studentId: body2.studentId,
124672
- action: body2.action
124673
- });
124674
- return ctx.services.timebackAdmin.toggleCourseCompletion(body2, ctx.user);
124675
- });
124676
124619
  searchStudents = requireGameManagementAccess(async (ctx) => {
124677
124620
  const gameId = ctx.params.gameId;
124678
124621
  const courseId = ctx.params.courseId;
@@ -124864,7 +124807,6 @@ var init_timeback_controller = __esm(() => {
124864
124807
  grantXp,
124865
124808
  adjustTime,
124866
124809
  adjustMastery,
124867
- toggleCompletion,
124868
124810
  searchStudents,
124869
124811
  enrollStudent,
124870
124812
  unenrollStudent,
@@ -127702,7 +127644,7 @@ var import_picocolors12 = __toESM(require_picocolors(), 1);
127702
127644
  // package.json
127703
127645
  var package_default2 = {
127704
127646
  name: "@playcademy/vite-plugin",
127705
- version: "0.2.30-beta.1",
127647
+ version: "0.2.30-beta.3",
127706
127648
  type: "module",
127707
127649
  exports: {
127708
127650
  ".": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@playcademy/vite-plugin",
3
- "version": "0.2.30-beta.1",
3
+ "version": "0.2.30-beta.3",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {