@playcademy/vite-plugin 0.2.22-beta.3 → 0.2.22-beta.4

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 +171 -46
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -25334,7 +25334,7 @@ var package_default;
25334
25334
  var init_package = __esm(() => {
25335
25335
  package_default = {
25336
25336
  name: "@playcademy/sandbox",
25337
- version: "0.3.16-beta.3",
25337
+ version: "0.3.16-beta.4",
25338
25338
  description: "Local development server for Playcademy game development",
25339
25339
  type: "module",
25340
25340
  exports: {
@@ -54095,16 +54095,7 @@ function mapAssessmentResultToRecentActivity(assessment, relevantCourseIds, cour
54095
54095
  return null;
54096
54096
  }
54097
54097
  if (isMasteryCompletionEntry(assessment)) {
54098
- const metadata3 = isRecord2(assessment.metadata) ? assessment.metadata : undefined;
54099
- const isResume = Boolean(metadata3?.resumedAt);
54100
- return {
54101
- id: assessment.sourcedId || `${assessment.assessmentLineItem.sourcedId}:${assessment.scoreDate}`,
54102
- kind: "admin-completion",
54103
- occurredAt: assessment.scoreDate,
54104
- courseId,
54105
- title: isResume ? "Course resumed" : "Course marked complete",
54106
- reason: metadata3?.adminAction ? "Admin action" : undefined
54107
- };
54098
+ return null;
54108
54099
  }
54109
54100
  const metadata2 = isRecord2(assessment.metadata) ? assessment.metadata : undefined;
54110
54101
  const activityName = getStringValue(metadata2?.activityName);
@@ -54137,6 +54128,7 @@ function parseCaliperEventContext(event, relevantCourseIds) {
54137
54128
  courseId,
54138
54129
  occurredAt,
54139
54130
  eventKind: getStringValue(playcademy?.eventKind),
54131
+ source: getStringValue(playcademy?.source),
54140
54132
  reason: getStringValue(playcademy?.reason),
54141
54133
  titleFromEvent: getStringValue(event.object.activity?.name),
54142
54134
  appName: getStringValue(event.object.app?.name),
@@ -54160,30 +54152,59 @@ function mapTimeSpentRemediation(event, ctx) {
54160
54152
  };
54161
54153
  }
54162
54154
  function mapActivityRemediation(event, ctx) {
54163
- let kind;
54164
54155
  if (ctx.eventKind === "remediation-xp") {
54165
- kind = "remediation-xp";
54166
- } else if (ctx.eventKind === "remediation-mastery") {
54167
- kind = "remediation-mastery";
54168
- } else {
54169
- return null;
54156
+ return {
54157
+ id: event.externalId,
54158
+ kind: "remediation-xp",
54159
+ occurredAt: ctx.occurredAt,
54160
+ courseId: ctx.courseId,
54161
+ title: "XP Adjustment",
54162
+ activityId: ctx.activityId,
54163
+ appName: ctx.appName,
54164
+ reason: ctx.reason,
54165
+ xpDelta: getGeneratedMetricValue(event, "xpEarned"),
54166
+ masteredUnitsDelta: getGeneratedMetricValue(event, "masteredUnits")
54167
+ };
54170
54168
  }
54171
- const titleMap = {
54172
- "remediation-xp": "XP Adjustment",
54173
- "remediation-mastery": "Mastery Adjustment"
54174
- };
54175
- return {
54176
- id: event.externalId,
54177
- kind,
54178
- occurredAt: ctx.occurredAt,
54179
- courseId: ctx.courseId,
54180
- title: titleMap[kind] || "Remediation Activity",
54181
- activityId: ctx.activityId,
54182
- appName: ctx.appName,
54183
- reason: ctx.reason,
54184
- xpDelta: getGeneratedMetricValue(event, "xpEarned"),
54185
- masteredUnitsDelta: getGeneratedMetricValue(event, "masteredUnits")
54186
- };
54169
+ if (ctx.eventKind === "remediation-mastery") {
54170
+ return {
54171
+ id: event.externalId,
54172
+ kind: "remediation-mastery",
54173
+ occurredAt: ctx.occurredAt,
54174
+ courseId: ctx.courseId,
54175
+ title: "Mastery Adjustment",
54176
+ activityId: ctx.activityId,
54177
+ appName: ctx.appName,
54178
+ reason: ctx.reason,
54179
+ xpDelta: getGeneratedMetricValue(event, "xpEarned"),
54180
+ masteredUnitsDelta: getGeneratedMetricValue(event, "masteredUnits")
54181
+ };
54182
+ }
54183
+ if (ctx.eventKind === "course-completed") {
54184
+ return {
54185
+ id: event.externalId,
54186
+ kind: "course-completed",
54187
+ occurredAt: ctx.occurredAt,
54188
+ courseId: ctx.courseId,
54189
+ title: ctx.source === "admin" ? "Course marked complete" : "Course completed",
54190
+ activityId: ctx.activityId,
54191
+ appName: ctx.appName,
54192
+ reason: ctx.reason
54193
+ };
54194
+ }
54195
+ if (ctx.eventKind === "course-resumed") {
54196
+ return {
54197
+ id: event.externalId,
54198
+ kind: "course-resumed",
54199
+ occurredAt: ctx.occurredAt,
54200
+ courseId: ctx.courseId,
54201
+ title: "Course resumed",
54202
+ activityId: ctx.activityId,
54203
+ appName: ctx.appName,
54204
+ reason: ctx.reason
54205
+ };
54206
+ }
54207
+ return null;
54187
54208
  }
54188
54209
  function mapCaliperEventToRemediationActivity(event, relevantCourseIds) {
54189
54210
  const ctx = parseCaliperEventContext(event, relevantCourseIds);
@@ -54204,6 +54225,7 @@ var init_timeback_util = __esm(() => {
54204
54225
 
54205
54226
  class TimebackAdminService {
54206
54227
  deps;
54228
+ static XP_PRECISION_FACTOR = 10;
54207
54229
  static RECENT_ACTIVITY_LIMIT = 20;
54208
54230
  static MAX_STUDENT_ACTIVITY_LIMIT = 200;
54209
54231
  static MAX_STUDENT_ACTIVITY_OFFSET = 1000;
@@ -54215,6 +54237,10 @@ class TimebackAdminService {
54215
54237
  constructor(deps) {
54216
54238
  this.deps = deps;
54217
54239
  }
54240
+ static roundXpToTenths(value) {
54241
+ const rounded = Math.round(value * TimebackAdminService.XP_PRECISION_FACTOR) / TimebackAdminService.XP_PRECISION_FACTOR;
54242
+ return Object.is(rounded, -0) ? 0 : rounded;
54243
+ }
54218
54244
  requireClient() {
54219
54245
  if (!this.deps.timeback) {
54220
54246
  logger16.error("Timeback client not available in context");
@@ -54222,6 +54248,17 @@ class TimebackAdminService {
54222
54248
  }
54223
54249
  return this.deps.timeback;
54224
54250
  }
54251
+ async recordCourseCompletionHistory(client, data) {
54252
+ await client.recordAdminCourseCompletionChange(data).catch((error) => {
54253
+ logger16.error("Failed to record admin course completion history event", {
54254
+ gameId: data.gameId,
54255
+ courseId: data.courseId,
54256
+ studentId: data.studentId,
54257
+ action: data.action,
54258
+ error: error instanceof Error ? error.message : String(error)
54259
+ });
54260
+ });
54261
+ }
54225
54262
  async resolveAdminMutationContext(gameId, courseId, user, studentId) {
54226
54263
  const client = this.requireClient();
54227
54264
  await this.deps.validateDeveloperAccess(user, gameId);
@@ -54261,8 +54298,8 @@ class TimebackAdminService {
54261
54298
  }
54262
54299
  const today = formatDateYMD();
54263
54300
  const history = [];
54264
- let totalXp = 0;
54265
- let todayXp = 0;
54301
+ let totalXpRaw = 0;
54302
+ let todayXpRaw = 0;
54266
54303
  let activeTimeSeconds = 0;
54267
54304
  let masteredUnits = 0;
54268
54305
  for (const [date3, subjectFacts] of Object.entries(facts)) {
@@ -54279,15 +54316,16 @@ class TimebackAdminService {
54279
54316
  masteredUnitsForDay += masteredUnitsFromFact;
54280
54317
  }
54281
54318
  }
54282
- totalXp += xpForDay;
54319
+ const roundedXpForDay = TimebackAdminService.roundXpToTenths(xpForDay);
54320
+ totalXpRaw += xpForDay;
54283
54321
  activeTimeSeconds += activeSecondsForDay;
54284
54322
  masteredUnits += masteredUnitsForDay;
54285
54323
  if (date3 === today) {
54286
- todayXp += xpForDay;
54324
+ todayXpRaw += xpForDay;
54287
54325
  }
54288
54326
  history.push({
54289
54327
  date: date3,
54290
- xpEarned: xpForDay,
54328
+ xpEarned: roundedXpForDay,
54291
54329
  activeTimeSeconds: activeSecondsForDay,
54292
54330
  masteredUnits: masteredUnitsForDay
54293
54331
  });
@@ -54295,8 +54333,8 @@ class TimebackAdminService {
54295
54333
  history.sort((a, b) => a.date.localeCompare(b.date));
54296
54334
  return {
54297
54335
  analyticsAvailable: true,
54298
- totalXp,
54299
- todayXp,
54336
+ totalXp: TimebackAdminService.roundXpToTenths(totalXpRaw),
54337
+ todayXp: TimebackAdminService.roundXpToTenths(todayXpRaw),
54300
54338
  activeTimeSeconds,
54301
54339
  masteredUnits,
54302
54340
  history
@@ -54638,6 +54676,7 @@ class TimebackAdminService {
54638
54676
  }
54639
54677
  async toggleCourseCompletion(data, user) {
54640
54678
  const { client, sensorUrl, appName, actor } = await this.resolveAdminMutationContext(data.gameId, data.courseId, user, data.studentId);
54679
+ const historyClient = client;
54641
54680
  const ids = deriveSourcedIds(data.courseId);
54642
54681
  const lineItemId = `${ids.course}-mastery-completion-assessment`;
54643
54682
  const resultId = `${lineItemId}:${data.studentId}:completion`;
@@ -54688,11 +54727,19 @@ class TimebackAdminService {
54688
54727
  inProgress: "false",
54689
54728
  metadata: {
54690
54729
  isMasteryCompletion: true,
54691
- completedAt: new Date().toISOString(),
54692
54730
  adminAction: true,
54693
54731
  appName
54694
54732
  }
54695
54733
  });
54734
+ await this.recordCourseCompletionHistory(historyClient, {
54735
+ gameId: data.gameId,
54736
+ courseId: data.courseId,
54737
+ studentId: data.studentId,
54738
+ action: "complete",
54739
+ actor,
54740
+ appName,
54741
+ sensorUrl
54742
+ });
54696
54743
  } else {
54697
54744
  await client.oneroster.assessmentResults.upsert(resultId, {
54698
54745
  sourcedId: resultId,
@@ -54705,11 +54752,19 @@ class TimebackAdminService {
54705
54752
  inProgress: "true",
54706
54753
  metadata: {
54707
54754
  isMasteryCompletion: true,
54708
- resumedAt: new Date().toISOString(),
54709
54755
  adminAction: true,
54710
54756
  appName
54711
54757
  }
54712
54758
  });
54759
+ await this.recordCourseCompletionHistory(historyClient, {
54760
+ gameId: data.gameId,
54761
+ courseId: data.courseId,
54762
+ studentId: data.studentId,
54763
+ action: "resume",
54764
+ actor,
54765
+ appName,
54766
+ sensorUrl
54767
+ });
54713
54768
  }
54714
54769
  return { status: "ok" };
54715
54770
  }
@@ -58934,7 +58989,8 @@ function buildAdminEventMetadata({
58934
58989
  return {
58935
58990
  playcademy: {
58936
58991
  eventKind,
58937
- reason
58992
+ reason,
58993
+ source: "admin"
58938
58994
  }
58939
58995
  };
58940
58996
  }
@@ -59050,6 +59106,30 @@ class AdminEventRecorder {
59050
59106
  eventExtensions: ctx.metadata
59051
59107
  });
59052
59108
  }
59109
+ async recordCourseCompletionChange(data) {
59110
+ const isResume = data.action === "resume";
59111
+ const ctx = await this.prepareAdminEvent({
59112
+ ...data,
59113
+ defaultActivityId: isResume ? "playcademy-admin-course-resumed" : "playcademy-admin-course-completed",
59114
+ reason: "Admin action",
59115
+ eventKind: isResume ? "course-resumed" : "course-completed"
59116
+ });
59117
+ await this.caliper.emitActivityEvent({
59118
+ studentId: ctx.student.id,
59119
+ studentEmail: ctx.student.email,
59120
+ activityId: ctx.activityId,
59121
+ activityName: isResume ? "Course resumed" : "Course marked complete",
59122
+ courseId: data.courseId,
59123
+ courseName: ctx.courseContext.courseName,
59124
+ subject: ctx.courseContext.subject,
59125
+ appName: ctx.appName,
59126
+ sensorUrl: ctx.sensorUrl,
59127
+ process: false,
59128
+ includeAttempt: false,
59129
+ generatedExtensions: ctx.metadata,
59130
+ eventExtensions: ctx.metadata
59131
+ });
59132
+ }
59053
59133
  }
59054
59134
 
59055
59135
  class TimebackCache {
@@ -59263,7 +59343,7 @@ class MasteryTracker {
59263
59343
  const totalMastered = historicalMasteredUnits + masteredUnits;
59264
59344
  const rawPct = totalMastered / masterableUnits * 100;
59265
59345
  const pctCompleteApp = Math.min(100, Math.max(0, Math.round(rawPct)));
59266
- const masteryAchieved = totalMastered >= masterableUnits;
59346
+ const masteryAchieved = historicalMasteredUnits < masterableUnits && totalMastered >= masterableUnits;
59267
59347
  return { pctCompleteApp, masteryAchieved };
59268
59348
  }
59269
59349
  async createCompletionEntry(studentId, courseId, classId, appName) {
@@ -59289,7 +59369,6 @@ class MasteryTracker {
59289
59369
  inProgress: "false",
59290
59370
  metadata: {
59291
59371
  isMasteryCompletion: true,
59292
- completedAt: new Date().toISOString(),
59293
59372
  appName
59294
59373
  }
59295
59374
  });
@@ -59505,6 +59584,16 @@ class ProgressRecorder {
59505
59584
  }
59506
59585
  if (masteryAchieved) {
59507
59586
  await this.masteryTracker.createCompletionEntry(studentId, courseId, progressData.classId, progressData.appName);
59587
+ await this.emitCourseCompletionHistoryEvent({
59588
+ studentId,
59589
+ studentEmail,
59590
+ activityId,
59591
+ courseId: ids.course,
59592
+ courseName,
59593
+ subject: progressData.subject,
59594
+ appName: progressData.appName,
59595
+ sensorUrl: progressData.sensorUrl
59596
+ });
59508
59597
  }
59509
59598
  await this.emitCaliperEvent({
59510
59599
  studentId,
@@ -59679,6 +59768,38 @@ class ProgressRecorder {
59679
59768
  log.error("[ProgressRecorder] Failed to emit activity event", { error });
59680
59769
  });
59681
59770
  }
59771
+ async emitCourseCompletionHistoryEvent(data) {
59772
+ await this.caliperNamespace.emitActivityEvent({
59773
+ studentId: data.studentId,
59774
+ studentEmail: data.studentEmail,
59775
+ activityId: data.activityId,
59776
+ activityName: "Course completed",
59777
+ courseId: data.courseId,
59778
+ courseName: data.courseName,
59779
+ subject: data.subject,
59780
+ appName: data.appName,
59781
+ sensorUrl: data.sensorUrl,
59782
+ process: false,
59783
+ includeAttempt: false,
59784
+ eventExtensions: {
59785
+ playcademy: {
59786
+ eventKind: "course-completed",
59787
+ source: "gameplay"
59788
+ }
59789
+ },
59790
+ generatedExtensions: {
59791
+ playcademy: {
59792
+ eventKind: "course-completed",
59793
+ source: "gameplay",
59794
+ activityId: data.activityId
59795
+ }
59796
+ }
59797
+ }).catch((error) => {
59798
+ log.error("[ProgressRecorder] Failed to emit course completion history event", {
59799
+ error
59800
+ });
59801
+ });
59802
+ }
59682
59803
  }
59683
59804
 
59684
59805
  class SessionRecorder {
@@ -60073,6 +60194,10 @@ class TimebackClient {
60073
60194
  await this._ensureAuthenticated();
60074
60195
  return this.adminEventRecorder.recordMasteryAdjustment(data);
60075
60196
  }
60197
+ async recordAdminCourseCompletionChange(data) {
60198
+ await this._ensureAuthenticated();
60199
+ return this.adminEventRecorder.recordCourseCompletionChange(data);
60200
+ }
60076
60201
  clearCaches() {
60077
60202
  this.cacheManager.clearAll();
60078
60203
  }
@@ -124862,7 +124987,7 @@ var import_picocolors12 = __toESM(require_picocolors(), 1);
124862
124987
  // package.json
124863
124988
  var package_default2 = {
124864
124989
  name: "@playcademy/vite-plugin",
124865
- version: "0.2.22-beta.3",
124990
+ version: "0.2.22-beta.4",
124866
124991
  type: "module",
124867
124992
  exports: {
124868
124993
  ".": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@playcademy/vite-plugin",
3
- "version": "0.2.22-beta.3",
3
+ "version": "0.2.22-beta.4",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {