@playcademy/sdk 0.16.0 → 0.16.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.
package/README.md CHANGED
@@ -116,6 +116,44 @@ await client.timeback.startActivity(
116
116
  )
117
117
  ```
118
118
 
119
+ Hosted assessments use the same methods locally and when deployed:
120
+
121
+ ```typescript
122
+ const attempt = await client.timeback.assessments.start({
123
+ activityId: 'math-diagnostic',
124
+ purpose: 'diagnostic',
125
+ subject: 'Math',
126
+ grade: 5,
127
+ })
128
+
129
+ const saved = await client.timeback.assessments.save(attempt.attemptId, {
130
+ expectedResponseVersion: attempt.responseVersion,
131
+ responses: {
132
+ [attempt.assessment.items[0].identifier]: { RESPONSE: 'A' },
133
+ },
134
+ })
135
+
136
+ const completed = await client.timeback.assessments.submit(attempt.attemptId, {
137
+ expectedResponseVersion: saved.responseVersion,
138
+ submissionId: crypto.randomUUID(),
139
+ })
140
+ ```
141
+
142
+ `start()` resumes an unfinished attempt or selects the next live test. Save payloads are
143
+ question deltas, but each included question replaces its complete response state; use `null` to
144
+ clear an answer. Keep the returned `responseVersion` and send it with the next mutation.
145
+
146
+ For local development, import the current ordered catalog before starting the dev host:
147
+
148
+ ```bash
149
+ playcademy timeback assessments import
150
+ playcademy dev
151
+ ```
152
+
153
+ The host reads `.playcademy/timeback/assessment-fixtures.json` and keeps local attempts in memory.
154
+ Restarting the dev host resets mutable attempt state. Deployed games use TimeBack automatically;
155
+ game code never selects the provider or reads fixture content.
156
+
119
157
  ### `client.backend`
120
158
 
121
159
  HTTP client for game-specific backend functions deployed via `playcademy deploy`.
package/dist/index.d.ts CHANGED
@@ -1037,6 +1037,13 @@ declare class PlaycademyClient extends PlaycademyBaseClient {
1037
1037
  * - `endActivity(scoreData)` - Submit activity results to TimeBack
1038
1038
  */
1039
1039
  timeback: {
1040
+ assessments: {
1041
+ start: (input: _playcademy_types.StartAssessmentInput) => Promise<_playcademy_types.AssessmentAttemptSnapshot>;
1042
+ latest: (options: _playcademy_types.GetLatestAssessmentOptions) => Promise<_playcademy_types.LatestAssessmentResult | null>;
1043
+ get: (attemptId: string) => Promise<_playcademy_types.AssessmentAttemptSnapshot>;
1044
+ save: (attemptId: string, input: _playcademy_types.SaveAssessmentInput) => Promise<_playcademy_types.AssessmentSaveResult>;
1045
+ submit: (attemptId: string, input: _playcademy_types.SubmitAssessmentInput) => Promise<_playcademy_types.AssessmentSubmitResult>;
1046
+ };
1040
1047
  readonly user: TimebackUser;
1041
1048
  readonly currentRunId: string | undefined;
1042
1049
  startActivity: (metadata: _playcademy_types.ActivityData, options?: StartActivityOptions) => StartActivityResult;
package/dist/index.js CHANGED
@@ -1118,25 +1118,6 @@ function createScoresNamespace(client) {
1118
1118
  }
1119
1119
  };
1120
1120
  }
1121
- // src/core/guards.ts
1122
- var VALID_GRADES = [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13];
1123
- var VALID_SUBJECTS = [
1124
- "Reading",
1125
- "Language",
1126
- "Vocabulary",
1127
- "Social Studies",
1128
- "Writing",
1129
- "Science",
1130
- "FastMath",
1131
- "Math",
1132
- "None"
1133
- ];
1134
- function isValidGrade(value) {
1135
- return typeof value === "number" && Number.isInteger(value) && VALID_GRADES.includes(value);
1136
- }
1137
- function isValidSubject(value) {
1138
- return typeof value === "string" && VALID_SUBJECTS.includes(value);
1139
- }
1140
1121
  // ../constants/src/platform.ts
1141
1122
  var PLAYCADEMY_BROWSER_TIME_ZONE_HEADER = "x-playcademy-browser-time-zone";
1142
1123
  // ../constants/src/timeback.ts
@@ -1147,8 +1128,22 @@ var TIMEBACK_ROUTES = {
1147
1128
  GET_HIGHEST_GRADE_MASTERED: "/integrations/timeback/highest-grade-mastered",
1148
1129
  HEARTBEAT: "/integrations/timeback/heartbeat",
1149
1130
  ADVANCE_COURSE: "/integrations/timeback/advance-course",
1150
- UNENROLL_COURSE: "/integrations/timeback/unenroll-course"
1131
+ UNENROLL_COURSE: "/integrations/timeback/unenroll-course",
1132
+ ASSESSMENTS: "/integrations/timeback/assessments"
1151
1133
  };
1134
+ var TIMEBACK_GRADES = [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13];
1135
+ var TIMEBACK_SUBJECTS = [
1136
+ "Reading",
1137
+ "Language",
1138
+ "Vocabulary",
1139
+ "Social Studies",
1140
+ "Writing",
1141
+ "Science",
1142
+ "FastMath",
1143
+ "Math",
1144
+ "None"
1145
+ ];
1146
+ var ASSESSMENT_PURPOSES = ["end_of_course", "diagnostic"];
1152
1147
  var TIMEBACK_GAME_METRIC_DECIMAL_PLACES = {
1153
1148
  xp: 1,
1154
1149
  mastery: 0,
@@ -1160,6 +1155,19 @@ var TIMEBACK_GAME_METRIC_COMPARISON_TOLERANCE = {
1160
1155
  time: 60,
1161
1156
  score: 0.5 / 10 ** TIMEBACK_GAME_METRIC_DECIMAL_PLACES.score
1162
1157
  };
1158
+ // src/core/guards.ts
1159
+ var VALID_GRADES = TIMEBACK_GRADES;
1160
+ var VALID_SUBJECTS = TIMEBACK_SUBJECTS;
1161
+ function isValidGrade(value) {
1162
+ return typeof value === "number" && Number.isInteger(value) && VALID_GRADES.includes(value);
1163
+ }
1164
+ function isValidSubject(value) {
1165
+ return typeof value === "string" && VALID_SUBJECTS.includes(value);
1166
+ }
1167
+ function isAssessmentPurpose(value) {
1168
+ return typeof value === "string" && ASSESSMENT_PURPOSES.includes(value);
1169
+ }
1170
+
1163
1171
  // src/core/cache/ttl-cache.ts
1164
1172
  function createTTLCache(options) {
1165
1173
  const cache = new Map;
@@ -2003,9 +2011,73 @@ function createTimebackEngine(client) {
2003
2011
  // src/namespaces/game/timeback.ts
2004
2012
  var VALID_XP_INCLUDE_OPTIONS = ["perCourse", "today"];
2005
2013
  var VALID_MASTERY_INCLUDE_OPTIONS = ["perCourse"];
2014
+ var ASSESSMENTS_ROUTE = TIMEBACK_ROUTES.ASSESSMENTS;
2015
+ function validateAssessmentFilters(options) {
2016
+ if (!isAssessmentPurpose(options?.purpose)) {
2017
+ throw new Error("purpose must be end_of_course or diagnostic");
2018
+ }
2019
+ if (options.grade !== undefined && !isValidGrade(options.grade)) {
2020
+ throw new Error(`Invalid grade: ${options.grade}. Valid grades: ${VALID_GRADES.join(", ")}`);
2021
+ }
2022
+ if (options.subject !== undefined && !isValidSubject(options.subject)) {
2023
+ throw new Error(`Invalid subject: ${options.subject}. Valid subjects: ${VALID_SUBJECTS.join(", ")}`);
2024
+ }
2025
+ }
2006
2026
  function createTimebackNamespace(client) {
2007
2027
  const engine = createTimebackEngine(client);
2008
2028
  return {
2029
+ assessments: {
2030
+ start: async (input) => {
2031
+ assertPlatformMode(client, "timeback.assessments.start()");
2032
+ if (!input.activityId?.trim()) {
2033
+ throw new Error("activityId is required");
2034
+ }
2035
+ validateAssessmentFilters(input);
2036
+ return client["requestGameBackend"](`${ASSESSMENTS_ROUTE}/start`, "POST", input);
2037
+ },
2038
+ latest: async (options) => {
2039
+ assertPlatformMode(client, "timeback.assessments.latest()");
2040
+ validateAssessmentFilters(options);
2041
+ const params = new URLSearchParams({ purpose: options.purpose });
2042
+ if (options.subject !== undefined) {
2043
+ params.set("subject", options.subject);
2044
+ }
2045
+ if (options.grade !== undefined) {
2046
+ params.set("grade", String(options.grade));
2047
+ }
2048
+ return client["requestGameBackend"](`${ASSESSMENTS_ROUTE}/latest?${params.toString()}`, "GET");
2049
+ },
2050
+ get: async (attemptId) => {
2051
+ assertPlatformMode(client, "timeback.assessments.get()");
2052
+ if (!attemptId?.trim()) {
2053
+ throw new Error("attemptId is required");
2054
+ }
2055
+ return client["requestGameBackend"](`${ASSESSMENTS_ROUTE}/${encodeURIComponent(attemptId)}`, "GET");
2056
+ },
2057
+ save: async (attemptId, input) => {
2058
+ assertPlatformMode(client, "timeback.assessments.save()");
2059
+ if (!attemptId?.trim()) {
2060
+ throw new Error("attemptId is required");
2061
+ }
2062
+ if (!Number.isInteger(input.expectedResponseVersion) || input.expectedResponseVersion < 0) {
2063
+ throw new Error("expectedResponseVersion must be a non-negative integer");
2064
+ }
2065
+ return client["requestGameBackend"](`${ASSESSMENTS_ROUTE}/${encodeURIComponent(attemptId)}/save`, "POST", input);
2066
+ },
2067
+ submit: async (attemptId, input) => {
2068
+ assertPlatformMode(client, "timeback.assessments.submit()");
2069
+ if (!attemptId?.trim()) {
2070
+ throw new Error("attemptId is required");
2071
+ }
2072
+ if (!Number.isInteger(input.expectedResponseVersion) || input.expectedResponseVersion < 0) {
2073
+ throw new Error("expectedResponseVersion must be a non-negative integer");
2074
+ }
2075
+ if (!input.submissionId?.trim()) {
2076
+ throw new Error("submissionId is required");
2077
+ }
2078
+ return client["requestGameBackend"](`${ASSESSMENTS_ROUTE}/${encodeURIComponent(attemptId)}/submit`, "POST", input);
2079
+ }
2080
+ },
2009
2081
  get user() {
2010
2082
  assertPlatformMode(client, "timeback.user");
2011
2083
  return {
@@ -2391,7 +2463,7 @@ async function request({
2391
2463
  return rawText && rawText.length > 0 ? rawText : undefined;
2392
2464
  }
2393
2465
  // src/version.ts
2394
- var SDK_VERSION = "0.16.0";
2466
+ var SDK_VERSION = "0.16.1-beta.1";
2395
2467
 
2396
2468
  // src/clients/base.ts
2397
2469
  class PlaycademyBaseClient {
@@ -3483,6 +3483,7 @@ declare class PlaycademyInternalClient extends PlaycademyBaseClient {
3483
3483
  reactivateCourse: (gameId: string, courseId: string) => Promise<_playcademy_types.GameTimebackIntegration>;
3484
3484
  getIntegrationConfig: (gameId: string, courseId: string) => Promise<_playcademy_types.GameTimebackIntegrationConfig>;
3485
3485
  getConfig: (gameId: string) => Promise<_playcademy_types.TimebackSetupRequest['config']>;
3486
+ exportAssessmentFixtures: (gameId: string) => Promise<_playcademy_types.AssessmentRuntimeFixtureBundle>;
3486
3487
  };
3487
3488
  students: {
3488
3489
  get: (timebackId: string, options?: TTLCacheConfig) => Promise<PlatformTimebackUserContext>;
package/dist/internal.js CHANGED
@@ -1118,25 +1118,6 @@ function createScoresNamespace(client) {
1118
1118
  }
1119
1119
  };
1120
1120
  }
1121
- // src/core/guards.ts
1122
- var VALID_GRADES = [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13];
1123
- var VALID_SUBJECTS = [
1124
- "Reading",
1125
- "Language",
1126
- "Vocabulary",
1127
- "Social Studies",
1128
- "Writing",
1129
- "Science",
1130
- "FastMath",
1131
- "Math",
1132
- "None"
1133
- ];
1134
- function isValidGrade(value) {
1135
- return typeof value === "number" && Number.isInteger(value) && VALID_GRADES.includes(value);
1136
- }
1137
- function isValidSubject(value) {
1138
- return typeof value === "string" && VALID_SUBJECTS.includes(value);
1139
- }
1140
1121
  // ../constants/src/platform.ts
1141
1122
  var PLAYCADEMY_BROWSER_TIME_ZONE_HEADER = "x-playcademy-browser-time-zone";
1142
1123
  // ../constants/src/timeback.ts
@@ -1147,8 +1128,22 @@ var TIMEBACK_ROUTES = {
1147
1128
  GET_HIGHEST_GRADE_MASTERED: "/integrations/timeback/highest-grade-mastered",
1148
1129
  HEARTBEAT: "/integrations/timeback/heartbeat",
1149
1130
  ADVANCE_COURSE: "/integrations/timeback/advance-course",
1150
- UNENROLL_COURSE: "/integrations/timeback/unenroll-course"
1131
+ UNENROLL_COURSE: "/integrations/timeback/unenroll-course",
1132
+ ASSESSMENTS: "/integrations/timeback/assessments"
1151
1133
  };
1134
+ var TIMEBACK_GRADES = [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13];
1135
+ var TIMEBACK_SUBJECTS = [
1136
+ "Reading",
1137
+ "Language",
1138
+ "Vocabulary",
1139
+ "Social Studies",
1140
+ "Writing",
1141
+ "Science",
1142
+ "FastMath",
1143
+ "Math",
1144
+ "None"
1145
+ ];
1146
+ var ASSESSMENT_PURPOSES = ["end_of_course", "diagnostic"];
1152
1147
  var TIMEBACK_GAME_METRIC_DECIMAL_PLACES = {
1153
1148
  xp: 1,
1154
1149
  mastery: 0,
@@ -1160,6 +1155,19 @@ var TIMEBACK_GAME_METRIC_COMPARISON_TOLERANCE = {
1160
1155
  time: 60,
1161
1156
  score: 0.5 / 10 ** TIMEBACK_GAME_METRIC_DECIMAL_PLACES.score
1162
1157
  };
1158
+ // src/core/guards.ts
1159
+ var VALID_GRADES = TIMEBACK_GRADES;
1160
+ var VALID_SUBJECTS = TIMEBACK_SUBJECTS;
1161
+ function isValidGrade(value) {
1162
+ return typeof value === "number" && Number.isInteger(value) && VALID_GRADES.includes(value);
1163
+ }
1164
+ function isValidSubject(value) {
1165
+ return typeof value === "string" && VALID_SUBJECTS.includes(value);
1166
+ }
1167
+ function isAssessmentPurpose(value) {
1168
+ return typeof value === "string" && ASSESSMENT_PURPOSES.includes(value);
1169
+ }
1170
+
1163
1171
  // src/core/cache/ttl-cache.ts
1164
1172
  function createTTLCache(options) {
1165
1173
  const cache = new Map;
@@ -2003,9 +2011,73 @@ function createTimebackEngine(client) {
2003
2011
  // src/namespaces/game/timeback.ts
2004
2012
  var VALID_XP_INCLUDE_OPTIONS = ["perCourse", "today"];
2005
2013
  var VALID_MASTERY_INCLUDE_OPTIONS = ["perCourse"];
2014
+ var ASSESSMENTS_ROUTE = TIMEBACK_ROUTES.ASSESSMENTS;
2015
+ function validateAssessmentFilters(options) {
2016
+ if (!isAssessmentPurpose(options?.purpose)) {
2017
+ throw new Error("purpose must be end_of_course or diagnostic");
2018
+ }
2019
+ if (options.grade !== undefined && !isValidGrade(options.grade)) {
2020
+ throw new Error(`Invalid grade: ${options.grade}. Valid grades: ${VALID_GRADES.join(", ")}`);
2021
+ }
2022
+ if (options.subject !== undefined && !isValidSubject(options.subject)) {
2023
+ throw new Error(`Invalid subject: ${options.subject}. Valid subjects: ${VALID_SUBJECTS.join(", ")}`);
2024
+ }
2025
+ }
2006
2026
  function createTimebackNamespace(client) {
2007
2027
  const engine = createTimebackEngine(client);
2008
2028
  return {
2029
+ assessments: {
2030
+ start: async (input) => {
2031
+ assertPlatformMode(client, "timeback.assessments.start()");
2032
+ if (!input.activityId?.trim()) {
2033
+ throw new Error("activityId is required");
2034
+ }
2035
+ validateAssessmentFilters(input);
2036
+ return client["requestGameBackend"](`${ASSESSMENTS_ROUTE}/start`, "POST", input);
2037
+ },
2038
+ latest: async (options) => {
2039
+ assertPlatformMode(client, "timeback.assessments.latest()");
2040
+ validateAssessmentFilters(options);
2041
+ const params = new URLSearchParams({ purpose: options.purpose });
2042
+ if (options.subject !== undefined) {
2043
+ params.set("subject", options.subject);
2044
+ }
2045
+ if (options.grade !== undefined) {
2046
+ params.set("grade", String(options.grade));
2047
+ }
2048
+ return client["requestGameBackend"](`${ASSESSMENTS_ROUTE}/latest?${params.toString()}`, "GET");
2049
+ },
2050
+ get: async (attemptId) => {
2051
+ assertPlatformMode(client, "timeback.assessments.get()");
2052
+ if (!attemptId?.trim()) {
2053
+ throw new Error("attemptId is required");
2054
+ }
2055
+ return client["requestGameBackend"](`${ASSESSMENTS_ROUTE}/${encodeURIComponent(attemptId)}`, "GET");
2056
+ },
2057
+ save: async (attemptId, input) => {
2058
+ assertPlatformMode(client, "timeback.assessments.save()");
2059
+ if (!attemptId?.trim()) {
2060
+ throw new Error("attemptId is required");
2061
+ }
2062
+ if (!Number.isInteger(input.expectedResponseVersion) || input.expectedResponseVersion < 0) {
2063
+ throw new Error("expectedResponseVersion must be a non-negative integer");
2064
+ }
2065
+ return client["requestGameBackend"](`${ASSESSMENTS_ROUTE}/${encodeURIComponent(attemptId)}/save`, "POST", input);
2066
+ },
2067
+ submit: async (attemptId, input) => {
2068
+ assertPlatformMode(client, "timeback.assessments.submit()");
2069
+ if (!attemptId?.trim()) {
2070
+ throw new Error("attemptId is required");
2071
+ }
2072
+ if (!Number.isInteger(input.expectedResponseVersion) || input.expectedResponseVersion < 0) {
2073
+ throw new Error("expectedResponseVersion must be a non-negative integer");
2074
+ }
2075
+ if (!input.submissionId?.trim()) {
2076
+ throw new Error("submissionId is required");
2077
+ }
2078
+ return client["requestGameBackend"](`${ASSESSMENTS_ROUTE}/${encodeURIComponent(attemptId)}/submit`, "POST", input);
2079
+ }
2080
+ },
2009
2081
  get user() {
2010
2082
  assertPlatformMode(client, "timeback.user");
2011
2083
  return {
@@ -2914,7 +2986,8 @@ function createTimebackNamespace2(client) {
2914
2986
  deactivateCourse: (gameId, courseId) => client["request"](`/timeback/integrations/${gameId}/${courseId}`, "DELETE"),
2915
2987
  reactivateCourse: (gameId, courseId) => client["request"](`/timeback/integrations/${gameId}/${courseId}/reactivate`, "POST"),
2916
2988
  getIntegrationConfig: (gameId, courseId) => client["request"](`/timeback/integrations/${gameId}/${courseId}`, "GET"),
2917
- getConfig: (gameId) => client["request"](`/timeback/config/${gameId}`, "GET")
2989
+ getConfig: (gameId) => client["request"](`/timeback/config/${gameId}`, "GET"),
2990
+ exportAssessmentFixtures: (gameId) => client["request"](`/timeback/assessments/fixtures/${gameId}`, "GET")
2918
2991
  },
2919
2992
  students: {
2920
2993
  get: async (timebackId, options) => studentCache.get(timebackId, async () => client["request"](`/timeback/user/${timebackId}`, "GET"), options),
@@ -3239,7 +3312,7 @@ async function request({
3239
3312
  return rawText && rawText.length > 0 ? rawText : undefined;
3240
3313
  }
3241
3314
  // src/version.ts
3242
- var SDK_VERSION = "0.16.0";
3315
+ var SDK_VERSION = "0.16.1-beta.1";
3243
3316
 
3244
3317
  // src/clients/base.ts
3245
3318
  class PlaycademyBaseClient {
@@ -346,6 +346,15 @@ declare class PlaycademyClient {
346
346
  get config(): PlaycademyServerClientState['config'];
347
347
  /** TimeBack integration methods (endActivity) */
348
348
  timeback: {
349
+ assessments: {
350
+ start: (studentId: string, input: _playcademy_types.StartAssessmentInput) => Promise<_playcademy_types.AssessmentAttemptSnapshot>;
351
+ latest: (studentId: string, options: _playcademy_types.GetLatestAssessmentOptions) => Promise<_playcademy_types.LatestAssessmentResult | null>;
352
+ get: (studentId: string, attemptId: string) => Promise<_playcademy_types.AssessmentAttemptSnapshot>;
353
+ save: (studentId: string, attemptId: string, input: _playcademy_types.SaveAssessmentInput) => Promise<_playcademy_types.AssessmentSaveResult>;
354
+ submit: (studentId: string, attemptId: string, input: _playcademy_types.SubmitAssessmentInput, context: {
355
+ sensorUrl: string;
356
+ }) => Promise<_playcademy_types.AssessmentSubmitResult>;
357
+ };
349
358
  endActivity: (studentId: string, payload: _playcademy_types.EndActivityPayload) => Promise<_playcademy_types.EndActivityResponse>;
350
359
  getStudentXp: (studentId: string, options?: {
351
360
  grade?: number;
@@ -9,10 +9,21 @@ var __export = (target, all) => {
9
9
  });
10
10
  };
11
11
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
12
-
13
- // src/core/guards.ts
14
- var VALID_GRADES = [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13];
15
- var VALID_SUBJECTS = [
12
+ // ../constants/src/platform.ts
13
+ var PLAYCADEMY_BROWSER_TIME_ZONE_HEADER = "x-playcademy-browser-time-zone";
14
+ // ../constants/src/timeback.ts
15
+ var TIMEBACK_ROUTES = {
16
+ END_ACTIVITY: "/integrations/timeback/end-activity",
17
+ GET_XP: "/integrations/timeback/xp",
18
+ GET_MASTERY: "/integrations/timeback/mastery",
19
+ GET_HIGHEST_GRADE_MASTERED: "/integrations/timeback/highest-grade-mastered",
20
+ HEARTBEAT: "/integrations/timeback/heartbeat",
21
+ ADVANCE_COURSE: "/integrations/timeback/advance-course",
22
+ UNENROLL_COURSE: "/integrations/timeback/unenroll-course",
23
+ ASSESSMENTS: "/integrations/timeback/assessments"
24
+ };
25
+ var TIMEBACK_GRADES = [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13];
26
+ var TIMEBACK_SUBJECTS = [
16
27
  "Reading",
17
28
  "Language",
18
29
  "Vocabulary",
@@ -23,12 +34,30 @@ var VALID_SUBJECTS = [
23
34
  "Math",
24
35
  "None"
25
36
  ];
37
+ var ASSESSMENT_PURPOSES = ["end_of_course", "diagnostic"];
38
+ var TIMEBACK_GAME_METRIC_DECIMAL_PLACES = {
39
+ xp: 1,
40
+ mastery: 0,
41
+ score: 2
42
+ };
43
+ var TIMEBACK_GAME_METRIC_COMPARISON_TOLERANCE = {
44
+ xp: 0.5 / 10 ** TIMEBACK_GAME_METRIC_DECIMAL_PLACES.xp,
45
+ mastery: 0,
46
+ time: 60,
47
+ score: 0.5 / 10 ** TIMEBACK_GAME_METRIC_DECIMAL_PLACES.score
48
+ };
49
+ // src/core/guards.ts
50
+ var VALID_GRADES = TIMEBACK_GRADES;
51
+ var VALID_SUBJECTS = TIMEBACK_SUBJECTS;
26
52
  function isValidGrade(value) {
27
53
  return typeof value === "number" && Number.isInteger(value) && VALID_GRADES.includes(value);
28
54
  }
29
55
  function isValidSubject(value) {
30
56
  return typeof value === "string" && VALID_SUBJECTS.includes(value);
31
57
  }
58
+ function isAssessmentPurpose(value) {
59
+ return typeof value === "string" && ASSESSMENT_PURPOSES.includes(value);
60
+ }
32
61
  // src/server/namespaces/timeback.ts
33
62
  function createTimebackNamespace(client) {
34
63
  function enrichActivityData(data) {
@@ -38,6 +67,35 @@ function createTimebackNamespace(client) {
38
67
  };
39
68
  }
40
69
  return {
70
+ assessments: {
71
+ start: (studentId, input) => client["request"]("/api/timeback/assessments/start", "POST", { ...input, gameId: client.gameId, studentId }),
72
+ latest: (studentId, options) => {
73
+ const params = new URLSearchParams({
74
+ gameId: client.gameId,
75
+ studentId,
76
+ purpose: options.purpose
77
+ });
78
+ if (options.subject !== undefined) {
79
+ params.set("subject", options.subject);
80
+ }
81
+ if (options.grade !== undefined) {
82
+ params.set("grade", String(options.grade));
83
+ }
84
+ return client["request"](`/api/timeback/assessments/latest?${params.toString()}`, "GET");
85
+ },
86
+ get: (studentId, attemptId) => {
87
+ const params = new URLSearchParams({ gameId: client.gameId, studentId });
88
+ return client["request"](`/api/timeback/assessments/${encodeURIComponent(attemptId)}?${params.toString()}`, "GET");
89
+ },
90
+ save: (studentId, attemptId, input) => client["request"](`/api/timeback/assessments/${encodeURIComponent(attemptId)}/save`, "POST", { ...input, gameId: client.gameId, studentId }),
91
+ submit: (studentId, attemptId, input, context) => client["request"](`/api/timeback/assessments/${encodeURIComponent(attemptId)}/submit`, "POST", {
92
+ ...input,
93
+ gameId: client.gameId,
94
+ studentId,
95
+ appName: client.config.name,
96
+ sensorUrl: context.sensorUrl
97
+ })
98
+ },
41
99
  endActivity: async (studentId, payload) => {
42
100
  if (!isValidGrade(payload.activityData.grade)) {
43
101
  throw new Error("activityData.grade must be a valid grade level (-1 to 13)");
@@ -234,7 +292,7 @@ function extractApiErrorInfo(error) {
234
292
  }
235
293
 
236
294
  // src/version.ts
237
- var SDK_VERSION = "0.16.0";
295
+ var SDK_VERSION = "0.16.1-beta.1";
238
296
 
239
297
  // src/server/request.ts
240
298
  async function makeApiRequest(opts) {
package/dist/server.d.ts CHANGED
@@ -346,6 +346,15 @@ declare class PlaycademyClient$1 {
346
346
  get config(): PlaycademyServerClientState['config'];
347
347
  /** TimeBack integration methods (endActivity) */
348
348
  timeback: {
349
+ assessments: {
350
+ start: (studentId: string, input: _playcademy_types.StartAssessmentInput) => Promise<_playcademy_types.AssessmentAttemptSnapshot>;
351
+ latest: (studentId: string, options: _playcademy_types.GetLatestAssessmentOptions) => Promise<_playcademy_types.LatestAssessmentResult | null>;
352
+ get: (studentId: string, attemptId: string) => Promise<_playcademy_types.AssessmentAttemptSnapshot>;
353
+ save: (studentId: string, attemptId: string, input: _playcademy_types.SaveAssessmentInput) => Promise<_playcademy_types.AssessmentSaveResult>;
354
+ submit: (studentId: string, attemptId: string, input: _playcademy_types.SubmitAssessmentInput, context: {
355
+ sensorUrl: string;
356
+ }) => Promise<_playcademy_types.AssessmentSubmitResult>;
357
+ };
349
358
  endActivity: (studentId: string, payload: _playcademy_types.EndActivityPayload) => Promise<_playcademy_types.EndActivityResponse>;
350
359
  getStudentXp: (studentId: string, options?: {
351
360
  grade?: number;
package/dist/server.js CHANGED
@@ -198,10 +198,21 @@ function validateConfig(config) {
198
198
  var init_config_loader = __esm(() => {
199
199
  init_file_loader();
200
200
  });
201
-
202
- // src/core/guards.ts
203
- var VALID_GRADES = [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13];
204
- var VALID_SUBJECTS = [
201
+ // ../constants/src/platform.ts
202
+ var PLAYCADEMY_BROWSER_TIME_ZONE_HEADER = "x-playcademy-browser-time-zone";
203
+ // ../constants/src/timeback.ts
204
+ var TIMEBACK_ROUTES = {
205
+ END_ACTIVITY: "/integrations/timeback/end-activity",
206
+ GET_XP: "/integrations/timeback/xp",
207
+ GET_MASTERY: "/integrations/timeback/mastery",
208
+ GET_HIGHEST_GRADE_MASTERED: "/integrations/timeback/highest-grade-mastered",
209
+ HEARTBEAT: "/integrations/timeback/heartbeat",
210
+ ADVANCE_COURSE: "/integrations/timeback/advance-course",
211
+ UNENROLL_COURSE: "/integrations/timeback/unenroll-course",
212
+ ASSESSMENTS: "/integrations/timeback/assessments"
213
+ };
214
+ var TIMEBACK_GRADES = [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13];
215
+ var TIMEBACK_SUBJECTS = [
205
216
  "Reading",
206
217
  "Language",
207
218
  "Vocabulary",
@@ -212,12 +223,30 @@ var VALID_SUBJECTS = [
212
223
  "Math",
213
224
  "None"
214
225
  ];
226
+ var ASSESSMENT_PURPOSES = ["end_of_course", "diagnostic"];
227
+ var TIMEBACK_GAME_METRIC_DECIMAL_PLACES = {
228
+ xp: 1,
229
+ mastery: 0,
230
+ score: 2
231
+ };
232
+ var TIMEBACK_GAME_METRIC_COMPARISON_TOLERANCE = {
233
+ xp: 0.5 / 10 ** TIMEBACK_GAME_METRIC_DECIMAL_PLACES.xp,
234
+ mastery: 0,
235
+ time: 60,
236
+ score: 0.5 / 10 ** TIMEBACK_GAME_METRIC_DECIMAL_PLACES.score
237
+ };
238
+ // src/core/guards.ts
239
+ var VALID_GRADES = TIMEBACK_GRADES;
240
+ var VALID_SUBJECTS = TIMEBACK_SUBJECTS;
215
241
  function isValidGrade(value) {
216
242
  return typeof value === "number" && Number.isInteger(value) && VALID_GRADES.includes(value);
217
243
  }
218
244
  function isValidSubject(value) {
219
245
  return typeof value === "string" && VALID_SUBJECTS.includes(value);
220
246
  }
247
+ function isAssessmentPurpose(value) {
248
+ return typeof value === "string" && ASSESSMENT_PURPOSES.includes(value);
249
+ }
221
250
  // src/server/namespaces/timeback.ts
222
251
  function createTimebackNamespace(client) {
223
252
  function enrichActivityData(data) {
@@ -227,6 +256,35 @@ function createTimebackNamespace(client) {
227
256
  };
228
257
  }
229
258
  return {
259
+ assessments: {
260
+ start: (studentId, input) => client["request"]("/api/timeback/assessments/start", "POST", { ...input, gameId: client.gameId, studentId }),
261
+ latest: (studentId, options) => {
262
+ const params = new URLSearchParams({
263
+ gameId: client.gameId,
264
+ studentId,
265
+ purpose: options.purpose
266
+ });
267
+ if (options.subject !== undefined) {
268
+ params.set("subject", options.subject);
269
+ }
270
+ if (options.grade !== undefined) {
271
+ params.set("grade", String(options.grade));
272
+ }
273
+ return client["request"](`/api/timeback/assessments/latest?${params.toString()}`, "GET");
274
+ },
275
+ get: (studentId, attemptId) => {
276
+ const params = new URLSearchParams({ gameId: client.gameId, studentId });
277
+ return client["request"](`/api/timeback/assessments/${encodeURIComponent(attemptId)}?${params.toString()}`, "GET");
278
+ },
279
+ save: (studentId, attemptId, input) => client["request"](`/api/timeback/assessments/${encodeURIComponent(attemptId)}/save`, "POST", { ...input, gameId: client.gameId, studentId }),
280
+ submit: (studentId, attemptId, input, context) => client["request"](`/api/timeback/assessments/${encodeURIComponent(attemptId)}/submit`, "POST", {
281
+ ...input,
282
+ gameId: client.gameId,
283
+ studentId,
284
+ appName: client.config.name,
285
+ sensorUrl: context.sensorUrl
286
+ })
287
+ },
230
288
  endActivity: async (studentId, payload) => {
231
289
  if (!isValidGrade(payload.activityData.grade)) {
232
290
  throw new Error("activityData.grade must be a valid grade level (-1 to 13)");
@@ -423,7 +481,7 @@ function extractApiErrorInfo(error) {
423
481
  }
424
482
 
425
483
  // src/version.ts
426
- var SDK_VERSION = "0.16.0";
484
+ var SDK_VERSION = "0.16.1-beta.1";
427
485
 
428
486
  // src/server/request.ts
429
487
  async function makeApiRequest(opts) {
package/dist/types.d.ts CHANGED
@@ -1465,6 +1465,13 @@ declare class PlaycademyClient extends PlaycademyBaseClient {
1465
1465
  * - `endActivity(scoreData)` - Submit activity results to TimeBack
1466
1466
  */
1467
1467
  timeback: {
1468
+ assessments: {
1469
+ start: (input: _playcademy_types.StartAssessmentInput) => Promise<_playcademy_types.AssessmentAttemptSnapshot>;
1470
+ latest: (options: _playcademy_types.GetLatestAssessmentOptions) => Promise<_playcademy_types.LatestAssessmentResult | null>;
1471
+ get: (attemptId: string) => Promise<_playcademy_types.AssessmentAttemptSnapshot>;
1472
+ save: (attemptId: string, input: _playcademy_types.SaveAssessmentInput) => Promise<_playcademy_types.AssessmentSaveResult>;
1473
+ submit: (attemptId: string, input: _playcademy_types.SubmitAssessmentInput) => Promise<_playcademy_types.AssessmentSubmitResult>;
1474
+ };
1468
1475
  readonly user: TimebackUser;
1469
1476
  readonly currentRunId: string | undefined;
1470
1477
  startActivity: (metadata: _playcademy_types.ActivityData, options?: StartActivityOptions) => StartActivityResult;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@playcademy/sdk",
3
- "version": "0.16.0",
3
+ "version": "0.16.1-beta.1",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {