@playcademy/vite-plugin 0.2.26-beta.5 → 0.2.26-beta.7

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 +126 -15
  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.20",
25373
+ version: "0.3.17-beta.22",
25374
25374
  description: "Local development server for Playcademy game development",
25375
25375
  type: "module",
25376
25376
  exports: {
@@ -54822,21 +54822,25 @@ class TimebackAdminService {
54822
54822
  });
54823
54823
  return new Map(results);
54824
54824
  }
54825
+ async fetchCaliperEventsForStudent(client, studentId, source, limit) {
54826
+ const actorId = `${client.getBaseUrl().replace(/\/$/, "")}/ims/oneroster/rostering/v1p2/users/${studentId}`;
54827
+ const { events } = await client.caliper.events.list({
54828
+ limit,
54829
+ actorId,
54830
+ ...source.sourceMode === "production" ? { sensor: source.sensorUrl } : {},
54831
+ extensions: {
54832
+ gameId: source.gameId
54833
+ }
54834
+ });
54835
+ return events;
54836
+ }
54825
54837
  async listRecentActivityForStudent(client, studentId, source, relevantCourseIds, maxResults = TimebackAdminService.RECENT_ACTIVITY_LIMIT) {
54826
54838
  if (relevantCourseIds.size === 0) {
54827
54839
  return [];
54828
54840
  }
54829
54841
  try {
54830
- const actorId = `${client.getBaseUrl().replace(/\/$/, "")}/ims/oneroster/rostering/v1p2/users/${studentId}`;
54831
54842
  const eventLimit = Math.min(Math.max(200, maxResults * 20), TimebackAdminService.MAX_RECENT_ACTIVITY_EVENT_FETCH);
54832
- const { events } = await client.caliper.events.list({
54833
- limit: eventLimit,
54834
- actorId,
54835
- ...source.sourceMode === "production" ? { sensor: source.sensorUrl } : {},
54836
- extensions: {
54837
- gameId: source.gameId
54838
- }
54839
- });
54843
+ const events = await this.fetchCaliperEventsForStudent(client, studentId, source, eventLimit);
54840
54844
  return TimebackAdminService.mapRecentActivityItems(events, relevantCourseIds).slice(0, maxResults);
54841
54845
  } catch (error) {
54842
54846
  logger16.warn("Failed to load recent Caliper activity", {
@@ -54960,6 +54964,42 @@ class TimebackAdminService {
54960
54964
  const hasMore = allActivities.length > safeOffset + safeLimit;
54961
54965
  return { activities, hasMore };
54962
54966
  }
54967
+ async getActivityDetail(user, options) {
54968
+ const { gameId, studentId, courseId, activityId, runId } = options;
54969
+ const client = this.requireClient();
54970
+ await this.deps.validateGameManagementAccess(user, gameId);
54971
+ const [integration, gameSource] = await Promise.all([
54972
+ this.deps.db.query.gameTimebackIntegrations.findFirst({
54973
+ where: and(eq(gameTimebackIntegrations.gameId, gameId), eq(gameTimebackIntegrations.courseId, courseId))
54974
+ }),
54975
+ this.getGameActivitySource(gameId)
54976
+ ]);
54977
+ if (!integration) {
54978
+ throw new NotFoundError("Timeback integration", `${gameId}:${courseId}`);
54979
+ }
54980
+ await this.assertStudentEnrolledInCourse(client, studentId, courseId);
54981
+ const events = await this.fetchCaliperEventsForStudent(client, studentId, gameSource, TimebackAdminService.MAX_RECENT_ACTIVITY_EVENT_FETCH);
54982
+ const relevantCourseIds = new Set([courseId]);
54983
+ let matchedEvents;
54984
+ let activity;
54985
+ if (runId) {
54986
+ const gameplayEvents = events.filter((event) => (event.type === "ActivityEvent" || event.type === "TimeSpentEvent") && !isCaliperRemediationOrCompletionEvent(event));
54987
+ const groups = groupCaliperEventsByRun(gameplayEvents);
54988
+ matchedEvents = [...groups.values()].find((group) => group.some((event) => getCanonicalRunId(event.session) === runId && event.externalId === activityId)) ?? [];
54989
+ activity = mapCaliperEventGroupToActivity(matchedEvents, relevantCourseIds);
54990
+ } else {
54991
+ matchedEvents = events.filter((event) => event.externalId === activityId);
54992
+ if (matchedEvents.length > 0) {
54993
+ activity = mapCaliperEventToRemediationActivity(matchedEvents[0], relevantCourseIds) ?? mapCaliperEventGroupToActivity(matchedEvents, relevantCourseIds);
54994
+ } else {
54995
+ activity = null;
54996
+ }
54997
+ }
54998
+ if (!activity) {
54999
+ throw new NotFoundError("Activity", activityId);
55000
+ }
55001
+ return { activity, rawEvents: matchedEvents };
55002
+ }
54963
55003
  async grantManualXp(data, user) {
54964
55004
  const { client, sensorUrl, appName, actor } = await this.resolveAdminMutationContext(data.gameId, data.courseId, user, data.studentId);
54965
55005
  await client.recordAdminXpAdjustment({
@@ -60133,7 +60173,7 @@ class MasteryTracker {
60133
60173
  }
60134
60174
  async checkProgress(input) {
60135
60175
  const { studentId, courseId, resourceId, masteredUnits } = input;
60136
- if (typeof masteredUnits !== "number" || masteredUnits <= 0) {
60176
+ if (typeof masteredUnits !== "number" || masteredUnits === 0) {
60137
60177
  return;
60138
60178
  }
60139
60179
  const status = await this.calculateStatus({
@@ -60145,9 +60185,11 @@ class MasteryTracker {
60145
60185
  if (!status) {
60146
60186
  return;
60147
60187
  }
60188
+ const wasComplete = status.historicalMasteredUnits >= status.masterableUnits;
60148
60189
  return {
60149
60190
  pctCompleteApp: status.pctCompleteApp,
60150
- masteryAchieved: status.historicalMasteredUnits < status.masterableUnits && status.isComplete
60191
+ masteryAchieved: !wasComplete && status.isComplete,
60192
+ masteryRevoked: wasComplete && !status.isComplete
60151
60193
  };
60152
60194
  }
60153
60195
  async getStatus(input) {
@@ -60188,7 +60230,7 @@ class MasteryTracker {
60188
60230
  return;
60189
60231
  }
60190
60232
  const historicalMasteredUnits = this.sumAnalyticsMetric(facts, "masteredUnits");
60191
- const totalMastered = historicalMasteredUnits + additionalMasteredUnits;
60233
+ const totalMastered = Math.max(0, historicalMasteredUnits + additionalMasteredUnits);
60192
60234
  const rawPct = totalMastered / masterableUnits * 100;
60193
60235
  const pctCompleteApp = Math.min(100, Math.max(0, Math.round(rawPct)));
60194
60236
  return {
@@ -60238,6 +60280,45 @@ class MasteryTracker {
60238
60280
  });
60239
60281
  }
60240
60282
  }
60283
+ async revokeCompletionEntry(studentId, courseId, classId, appName) {
60284
+ const ids = deriveSourcedIds2(courseId);
60285
+ const lineItemId = `${ids.course}-mastery-completion-assessment`;
60286
+ const resultId = `${lineItemId}:${studentId}:completion`;
60287
+ try {
60288
+ await this.onerosterNamespace.assessmentLineItems.findOrCreate(lineItemId, {
60289
+ sourcedId: lineItemId,
60290
+ title: "Mastery Completion",
60291
+ status: ONEROSTER_STATUS4.active,
60292
+ ...classId ? { class: { sourcedId: classId } } : { course: { sourcedId: ids.course } },
60293
+ ...ids.componentResource ? { componentResource: { sourcedId: ids.componentResource } } : {}
60294
+ });
60295
+ await this.onerosterNamespace.assessmentResults.upsert(resultId, {
60296
+ sourcedId: resultId,
60297
+ status: ONEROSTER_STATUS4.active,
60298
+ assessmentLineItem: { sourcedId: lineItemId },
60299
+ student: { sourcedId: studentId },
60300
+ score: 0,
60301
+ scoreDate: new Date().toISOString(),
60302
+ scoreStatus: SCORE_STATUS4.notSubmitted,
60303
+ inProgress: "true",
60304
+ metadata: {
60305
+ isMasteryCompletion: true,
60306
+ appName
60307
+ }
60308
+ });
60309
+ log.info("[MasteryTracker] Revoked mastery completion entry", {
60310
+ studentId,
60311
+ lineItemId,
60312
+ resultId
60313
+ });
60314
+ } catch (error) {
60315
+ log.error("[MasteryTracker] Failed to revoke mastery completion entry", {
60316
+ studentId,
60317
+ lineItemId,
60318
+ error
60319
+ });
60320
+ }
60321
+ }
60241
60322
  async resolveMasterableUnits(resourceId) {
60242
60323
  if (!resourceId) {
60243
60324
  return;
@@ -60447,6 +60528,9 @@ class ProgressRecorder {
60447
60528
  sensorUrl: progressData.sensorUrl
60448
60529
  });
60449
60530
  }
60531
+ if (masteryProgress?.masteryRevoked) {
60532
+ await this.masteryTracker.revokeCompletionEntry(studentId, courseId, progressData.classId, progressData.appName);
60533
+ }
60450
60534
  await this.emitCaliperEvent({
60451
60535
  studentId,
60452
60536
  studentEmail,
@@ -120742,7 +120826,7 @@ var init_schemas11 = __esm(() => {
120742
120826
  inactiveSeconds: exports_external.number().nonnegative().optional()
120743
120827
  }).optional(),
120744
120828
  xpEarned: exports_external.number().optional(),
120745
- masteredUnits: exports_external.number().nonnegative().optional(),
120829
+ masteredUnits: exports_external.number().optional(),
120746
120830
  extensions: exports_external.record(exports_external.string(), exports_external.unknown()).optional()
120747
120831
  });
120748
120832
  AdvanceCourseRequestSchema = exports_external.object({
@@ -123103,6 +123187,7 @@ var getStudentXp;
123103
123187
  var getRoster;
123104
123188
  var getStudentOverview;
123105
123189
  var getStudentActivity;
123190
+ var getActivityDetail;
123106
123191
  var grantXp;
123107
123192
  var adjustTime;
123108
123193
  var adjustMastery;
@@ -123446,6 +123531,31 @@ var init_timeback_controller = __esm(() => {
123446
123531
  offset
123447
123532
  });
123448
123533
  });
123534
+ getActivityDetail = requireGameManagementAccess(async (ctx) => {
123535
+ const timebackId = ctx.params.timebackId;
123536
+ const courseId = ctx.params.courseId;
123537
+ const activityId = ctx.params.activityId;
123538
+ const gameId = ctx.url.searchParams.get("gameId") || undefined;
123539
+ const runId = ctx.url.searchParams.get("runId") || undefined;
123540
+ if (!timebackId || !courseId || !activityId || !gameId) {
123541
+ throw ApiError.badRequest("Missing timebackId, courseId, or activityId path parameter, or gameId query parameter");
123542
+ }
123543
+ logger64.debug("Getting activity detail", {
123544
+ requesterId: ctx.user.id,
123545
+ timebackId,
123546
+ courseId,
123547
+ activityId,
123548
+ gameId,
123549
+ runId
123550
+ });
123551
+ return ctx.services.timebackAdmin.getActivityDetail(ctx.user, {
123552
+ gameId,
123553
+ studentId: timebackId,
123554
+ courseId,
123555
+ activityId,
123556
+ runId
123557
+ });
123558
+ });
123449
123559
  grantXp = requireDeveloper(async (ctx) => {
123450
123560
  const body2 = await parseRequestBody(ctx.request, GrantTimebackXpRequestSchema);
123451
123561
  logger64.debug("Granting manual XP", {
@@ -123548,6 +123658,7 @@ var init_timeback_controller = __esm(() => {
123548
123658
  getRoster,
123549
123659
  getStudentOverview,
123550
123660
  getStudentActivity,
123661
+ getActivityDetail,
123551
123662
  grantXp,
123552
123663
  adjustTime,
123553
123664
  adjustMastery,
@@ -126369,7 +126480,7 @@ var import_picocolors12 = __toESM(require_picocolors(), 1);
126369
126480
  // package.json
126370
126481
  var package_default2 = {
126371
126482
  name: "@playcademy/vite-plugin",
126372
- version: "0.2.26-beta.5",
126483
+ version: "0.2.26-beta.7",
126373
126484
  type: "module",
126374
126485
  exports: {
126375
126486
  ".": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@playcademy/vite-plugin",
3
- "version": "0.2.26-beta.5",
3
+ "version": "0.2.26-beta.7",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {