@goscribe/server 1.6.0 → 1.7.0

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 (281) hide show
  1. package/dist/generated/prisma/client.d.ts +224 -0
  2. package/dist/generated/prisma/client.js +34 -0
  3. package/dist/generated/prisma/commonInputTypes.d.ts +941 -0
  4. package/dist/generated/prisma/commonInputTypes.js +10 -0
  5. package/dist/generated/prisma/enums.d.ts +67 -0
  6. package/dist/generated/prisma/enums.js +66 -0
  7. package/dist/generated/prisma/internal/class.d.ts +539 -0
  8. package/dist/generated/prisma/internal/class.js +49 -0
  9. package/dist/generated/prisma/internal/prismaNamespace.d.ts +3924 -0
  10. package/dist/generated/prisma/internal/prismaNamespace.js +557 -0
  11. package/dist/generated/prisma/models/ActivityLog.d.ts +1847 -0
  12. package/dist/generated/prisma/models/ActivityLog.js +1 -0
  13. package/dist/generated/prisma/models/Artifact.d.ts +2345 -0
  14. package/dist/generated/prisma/models/Artifact.js +1 -0
  15. package/dist/generated/prisma/models/ArtifactVersion.d.ts +1550 -0
  16. package/dist/generated/prisma/models/ArtifactVersion.js +1 -0
  17. package/dist/generated/prisma/models/Channel.d.ts +1257 -0
  18. package/dist/generated/prisma/models/Channel.js +1 -0
  19. package/dist/generated/prisma/models/Chat.d.ts +1339 -0
  20. package/dist/generated/prisma/models/Chat.js +1 -0
  21. package/dist/generated/prisma/models/CopilotConversation.d.ts +1450 -0
  22. package/dist/generated/prisma/models/CopilotConversation.js +1 -0
  23. package/dist/generated/prisma/models/CopilotMessage.d.ts +1179 -0
  24. package/dist/generated/prisma/models/CopilotMessage.js +1 -0
  25. package/dist/generated/prisma/models/FileAsset.d.ts +1832 -0
  26. package/dist/generated/prisma/models/FileAsset.js +1 -0
  27. package/dist/generated/prisma/models/Flashcard.d.ts +1460 -0
  28. package/dist/generated/prisma/models/Flashcard.js +1 -0
  29. package/dist/generated/prisma/models/FlashcardProgress.d.ts +1782 -0
  30. package/dist/generated/prisma/models/FlashcardProgress.js +1 -0
  31. package/dist/generated/prisma/models/Folder.d.ts +1685 -0
  32. package/dist/generated/prisma/models/Folder.js +1 -0
  33. package/dist/generated/prisma/models/IdempotencyRecord.d.ts +1319 -0
  34. package/dist/generated/prisma/models/IdempotencyRecord.js +1 -0
  35. package/dist/generated/prisma/models/Invoice.d.ts +1586 -0
  36. package/dist/generated/prisma/models/Invoice.js +1 -0
  37. package/dist/generated/prisma/models/KnowledgeBase.d.ts +1721 -0
  38. package/dist/generated/prisma/models/KnowledgeBase.js +1 -0
  39. package/dist/generated/prisma/models/KnowledgeBaseChunk.d.ts +1333 -0
  40. package/dist/generated/prisma/models/KnowledgeBaseChunk.js +1 -0
  41. package/dist/generated/prisma/models/KnowledgeBaseDocument.d.ts +1695 -0
  42. package/dist/generated/prisma/models/KnowledgeBaseDocument.js +1 -0
  43. package/dist/generated/prisma/models/Notification.d.ts +1992 -0
  44. package/dist/generated/prisma/models/Notification.js +1 -0
  45. package/dist/generated/prisma/models/PasswordResetToken.d.ts +1210 -0
  46. package/dist/generated/prisma/models/PasswordResetToken.js +1 -0
  47. package/dist/generated/prisma/models/Plan.d.ts +1431 -0
  48. package/dist/generated/prisma/models/Plan.js +1 -0
  49. package/dist/generated/prisma/models/PlanLimit.d.ts +1328 -0
  50. package/dist/generated/prisma/models/PlanLimit.js +1 -0
  51. package/dist/generated/prisma/models/PodcastSegment.d.ts +1564 -0
  52. package/dist/generated/prisma/models/PodcastSegment.js +1 -0
  53. package/dist/generated/prisma/models/ResourcePrice.d.ts +1008 -0
  54. package/dist/generated/prisma/models/ResourcePrice.js +1 -0
  55. package/dist/generated/prisma/models/Role.d.ts +1065 -0
  56. package/dist/generated/prisma/models/Role.js +1 -0
  57. package/dist/generated/prisma/models/Session.d.ts +1105 -0
  58. package/dist/generated/prisma/models/Session.js +1 -0
  59. package/dist/generated/prisma/models/StripeEvent.d.ts +1081 -0
  60. package/dist/generated/prisma/models/StripeEvent.js +1 -0
  61. package/dist/generated/prisma/models/StudyGuideComment.d.ts +1321 -0
  62. package/dist/generated/prisma/models/StudyGuideComment.js +1 -0
  63. package/dist/generated/prisma/models/StudyGuideHighlight.d.ts +1629 -0
  64. package/dist/generated/prisma/models/StudyGuideHighlight.js +1 -0
  65. package/dist/generated/prisma/models/Subscription.d.ts +1677 -0
  66. package/dist/generated/prisma/models/Subscription.js +1 -0
  67. package/dist/generated/prisma/models/User.d.ts +7559 -0
  68. package/dist/generated/prisma/models/User.js +1 -0
  69. package/dist/generated/prisma/models/UserCredit.d.ts +1249 -0
  70. package/dist/generated/prisma/models/UserCredit.js +1 -0
  71. package/dist/generated/prisma/models/VerificationToken.d.ts +946 -0
  72. package/dist/generated/prisma/models/VerificationToken.js +1 -0
  73. package/dist/generated/prisma/models/WorksheetPreset.d.ts +1433 -0
  74. package/dist/generated/prisma/models/WorksheetPreset.js +1 -0
  75. package/dist/generated/prisma/models/WorksheetQuestion.d.ts +1491 -0
  76. package/dist/generated/prisma/models/WorksheetQuestion.js +1 -0
  77. package/dist/generated/prisma/models/WorksheetQuestionProgress.d.ts +1620 -0
  78. package/dist/generated/prisma/models/WorksheetQuestionProgress.js +1 -0
  79. package/dist/generated/prisma/models/Workspace.d.ts +3620 -0
  80. package/dist/generated/prisma/models/Workspace.js +1 -0
  81. package/dist/generated/prisma/models/WorkspaceInvitation.d.ts +1490 -0
  82. package/dist/generated/prisma/models/WorkspaceInvitation.js +1 -0
  83. package/dist/generated/prisma/models/WorkspaceKnowledgeBase.d.ts +1410 -0
  84. package/dist/generated/prisma/models/WorkspaceKnowledgeBase.js +1 -0
  85. package/dist/generated/prisma/models/WorkspaceMember.d.ts +1326 -0
  86. package/dist/generated/prisma/models/WorkspaceMember.js +1 -0
  87. package/dist/generated/prisma/models.d.ts +39 -0
  88. package/dist/generated/prisma/models.js +1 -0
  89. package/dist/src/context.d.ts +27 -0
  90. package/dist/src/context.js +33 -0
  91. package/dist/src/index.d.ts +3 -0
  92. package/dist/src/index.js +1 -0
  93. package/dist/src/lib/ai/config.d.ts +20 -0
  94. package/dist/src/lib/ai/config.js +31 -0
  95. package/dist/src/lib/ai/embedding-client.d.ts +8 -0
  96. package/dist/src/lib/ai/embedding-client.js +30 -0
  97. package/dist/src/lib/ai/index.d.ts +48 -0
  98. package/dist/src/lib/ai/index.js +29 -0
  99. package/dist/src/lib/ai/inference-backend/client.d.ts +28 -0
  100. package/dist/src/lib/ai/inference-backend/client.js +301 -0
  101. package/dist/src/lib/ai/inference-backend/mocks.d.ts +12 -0
  102. package/dist/src/lib/ai/inference-backend/mocks.js +133 -0
  103. package/dist/src/lib/ai/inference-backend/types.d.ts +44 -0
  104. package/dist/src/lib/ai/inference-backend/types.js +1 -0
  105. package/dist/src/lib/ai/json-parse.d.ts +2 -0
  106. package/dist/src/lib/ai/json-parse.js +34 -0
  107. package/dist/src/lib/ai/llm-client.d.ts +7 -0
  108. package/dist/src/lib/ai/llm-client.js +36 -0
  109. package/dist/src/lib/ai/mock.d.ts +2 -0
  110. package/dist/src/lib/ai/mock.js +10 -0
  111. package/dist/src/lib/ai/types.d.ts +9 -0
  112. package/dist/src/lib/ai/types.js +1 -0
  113. package/dist/src/lib/auth.d.ts +36 -0
  114. package/dist/src/lib/auth.js +117 -0
  115. package/dist/src/lib/chunking.d.ts +19 -0
  116. package/dist/src/lib/chunking.js +47 -0
  117. package/dist/src/lib/constants.d.ts +13 -0
  118. package/dist/src/lib/constants.js +12 -0
  119. package/dist/src/lib/curated-kb-seed.d.ts +12 -0
  120. package/dist/src/lib/curated-kb-seed.js +155 -0
  121. package/dist/src/lib/email.d.ts +11 -0
  122. package/dist/src/lib/email.js +152 -0
  123. package/dist/src/lib/embeddings.d.ts +2 -0
  124. package/dist/src/lib/embeddings.js +1 -0
  125. package/dist/src/lib/ensure-curated-kb-catalog.d.ts +6 -0
  126. package/dist/src/lib/ensure-curated-kb-catalog.js +53 -0
  127. package/dist/src/lib/env.d.ts +41 -0
  128. package/dist/src/lib/env.js +57 -0
  129. package/dist/src/lib/errors.d.ts +33 -0
  130. package/dist/src/lib/errors.js +78 -0
  131. package/dist/src/lib/file.d.ts +0 -0
  132. package/dist/src/lib/file.js +1 -0
  133. package/dist/src/lib/inference.d.ts +1 -0
  134. package/dist/src/lib/inference.js +1 -0
  135. package/dist/src/lib/kb-meta.d.ts +8 -0
  136. package/dist/src/lib/kb-meta.js +77 -0
  137. package/dist/src/lib/logger.d.ts +62 -0
  138. package/dist/src/lib/logger.js +364 -0
  139. package/dist/src/lib/pdf.d.ts +11 -0
  140. package/dist/src/lib/pdf.js +11 -0
  141. package/dist/src/lib/prisma.d.ts +3 -0
  142. package/dist/src/lib/prisma.js +15 -0
  143. package/dist/src/lib/pusher.d.ts +38 -0
  144. package/dist/src/lib/pusher.js +170 -0
  145. package/dist/src/lib/retry.d.ts +15 -0
  146. package/dist/src/lib/retry.js +37 -0
  147. package/dist/src/lib/storage.d.ts +11 -0
  148. package/dist/src/lib/storage.js +71 -0
  149. package/dist/src/lib/stripe.d.ts +10 -0
  150. package/dist/src/lib/stripe.js +36 -0
  151. package/dist/src/lib/validation.d.ts +51 -0
  152. package/dist/src/lib/validation.js +64 -0
  153. package/dist/src/lib/workspace-kb.d.ts +5 -0
  154. package/dist/src/lib/workspace-kb.js +7 -0
  155. package/dist/src/repositories/artifact.repository.d.ts +64 -0
  156. package/dist/src/repositories/artifact.repository.js +40 -0
  157. package/dist/src/repositories/base.repository.d.ts +14 -0
  158. package/dist/src/repositories/base.repository.js +14 -0
  159. package/dist/src/repositories/invitation.repository.d.ts +104 -0
  160. package/dist/src/repositories/invitation.repository.js +44 -0
  161. package/dist/src/repositories/notification.repository.d.ts +76 -0
  162. package/dist/src/repositories/notification.repository.js +44 -0
  163. package/dist/src/repositories/user.repository.d.ts +84 -0
  164. package/dist/src/repositories/user.repository.js +37 -0
  165. package/dist/src/repositories/workspace-member.repository.d.ts +35 -0
  166. package/dist/src/repositories/workspace-member.repository.js +31 -0
  167. package/dist/src/repositories/workspace.repository.d.ts +101 -0
  168. package/dist/src/repositories/workspace.repository.js +79 -0
  169. package/dist/src/routers/_app.d.ts +3464 -0
  170. package/dist/src/routers/_app.js +36 -0
  171. package/dist/src/routers/admin.d.ts +358 -0
  172. package/dist/src/routers/admin.js +105 -0
  173. package/dist/src/routers/annotations.d.ts +219 -0
  174. package/dist/src/routers/annotations.js +29 -0
  175. package/dist/src/routers/artifactVersions.d.ts +65 -0
  176. package/dist/src/routers/artifactVersions.js +14 -0
  177. package/dist/src/routers/auth.d.ts +161 -0
  178. package/dist/src/routers/auth.js +97 -0
  179. package/dist/src/routers/chat.d.ts +170 -0
  180. package/dist/src/routers/chat.js +32 -0
  181. package/dist/src/routers/copilot.d.ts +200 -0
  182. package/dist/src/routers/copilot.js +52 -0
  183. package/dist/src/routers/flashcards.d.ts +336 -0
  184. package/dist/src/routers/flashcards.js +93 -0
  185. package/dist/src/routers/knowledgeBase.d.ts +421 -0
  186. package/dist/src/routers/knowledgeBase.js +118 -0
  187. package/dist/src/routers/members.d.ts +169 -0
  188. package/dist/src/routers/members.js +47 -0
  189. package/dist/src/routers/notifications.d.ts +99 -0
  190. package/dist/src/routers/notifications.js +25 -0
  191. package/dist/src/routers/payment.d.ts +80 -0
  192. package/dist/src/routers/payment.js +21 -0
  193. package/dist/src/routers/podcast.d.ts +287 -0
  194. package/dist/src/routers/podcast.js +34 -0
  195. package/dist/src/routers/studyguide.d.ts +36 -0
  196. package/dist/src/routers/studyguide.js +8 -0
  197. package/dist/src/routers/worksheets.d.ts +429 -0
  198. package/dist/src/routers/worksheets.js +139 -0
  199. package/dist/src/routers/workspace.d.ts +563 -0
  200. package/dist/src/routers/workspace.js +104 -0
  201. package/dist/src/scripts/purge-deleted-users.d.ts +1 -0
  202. package/dist/src/scripts/purge-deleted-users.js +148 -0
  203. package/dist/src/server.d.ts +1 -0
  204. package/dist/src/server.js +190 -0
  205. package/dist/src/services/activity/activity-human-description.service.d.ts +13 -0
  206. package/dist/src/services/activity/activity-human-description.service.js +221 -0
  207. package/dist/src/services/activity/activity-human-description.service.test.d.ts +1 -0
  208. package/dist/src/services/activity/activity-human-description.service.test.js +16 -0
  209. package/dist/src/services/activity/activity-log.service.d.ts +87 -0
  210. package/dist/src/services/activity/activity-log.service.js +276 -0
  211. package/dist/src/services/activity/activity-log.service.test.d.ts +1 -0
  212. package/dist/src/services/activity/activity-log.service.test.js +27 -0
  213. package/dist/src/services/admin/admin.service.d.ts +270 -0
  214. package/dist/src/services/admin/admin.service.js +476 -0
  215. package/dist/src/services/ai/ai-session.service.d.ts +5 -0
  216. package/dist/src/services/ai/ai-session.service.js +4 -0
  217. package/dist/src/services/artifacts/annotation.service.d.ts +177 -0
  218. package/dist/src/services/artifacts/annotation.service.js +154 -0
  219. package/dist/src/services/artifacts/artifact-version.service.d.ts +38 -0
  220. package/dist/src/services/artifacts/artifact-version.service.js +129 -0
  221. package/dist/src/services/artifacts/chat.service.d.ts +127 -0
  222. package/dist/src/services/artifacts/chat.service.js +182 -0
  223. package/dist/src/services/artifacts/study-guide.service.d.ts +18 -0
  224. package/dist/src/services/artifacts/study-guide.service.js +65 -0
  225. package/dist/src/services/auth/auth.service.d.ts +94 -0
  226. package/dist/src/services/auth/auth.service.js +368 -0
  227. package/dist/src/services/base.service.d.ts +14 -0
  228. package/dist/src/services/base.service.js +14 -0
  229. package/dist/src/services/billing/payment.service.d.ts +44 -0
  230. package/dist/src/services/billing/payment.service.js +365 -0
  231. package/dist/src/services/billing/subscription.service.d.ts +37 -0
  232. package/dist/src/services/billing/subscription.service.js +654 -0
  233. package/dist/src/services/billing/usage.service.d.ts +47 -0
  234. package/dist/src/services/billing/usage.service.js +149 -0
  235. package/dist/src/services/content/copilot.service.d.ts +113 -0
  236. package/dist/src/services/content/copilot.service.js +439 -0
  237. package/dist/src/services/content/flashcard-progress.service.d.ts +159 -0
  238. package/dist/src/services/content/flashcard-progress.service.js +432 -0
  239. package/dist/src/services/content/flashcard.service.d.ts +184 -0
  240. package/dist/src/services/content/flashcard.service.js +339 -0
  241. package/dist/src/services/content/media-analysis.service.d.ts +23 -0
  242. package/dist/src/services/content/media-analysis.service.js +404 -0
  243. package/dist/src/services/content/podcast.service.d.ts +267 -0
  244. package/dist/src/services/content/podcast.service.js +653 -0
  245. package/dist/src/services/content/worksheet-content.service.d.ts +37 -0
  246. package/dist/src/services/content/worksheet-content.service.js +84 -0
  247. package/dist/src/services/content/worksheet-content.service.test.d.ts +1 -0
  248. package/dist/src/services/content/worksheet-content.service.test.js +69 -0
  249. package/dist/src/services/content/worksheet-generation.service.d.ts +91 -0
  250. package/dist/src/services/content/worksheet-generation.service.js +95 -0
  251. package/dist/src/services/content/worksheet-generation.service.test.d.ts +1 -0
  252. package/dist/src/services/content/worksheet-generation.service.test.js +20 -0
  253. package/dist/src/services/content/worksheet.service.d.ts +347 -0
  254. package/dist/src/services/content/worksheet.service.js +599 -0
  255. package/dist/src/services/knowledge/knowledge-base.service.d.ts +316 -0
  256. package/dist/src/services/knowledge/knowledge-base.service.js +544 -0
  257. package/dist/src/services/members/invitation.service.d.ts +66 -0
  258. package/dist/src/services/members/invitation.service.js +348 -0
  259. package/dist/src/services/members/member.service.d.ts +36 -0
  260. package/dist/src/services/members/member.service.js +193 -0
  261. package/dist/src/services/notifications/notification.service.d.ts +214 -0
  262. package/dist/src/services/notifications/notification.service.js +550 -0
  263. package/dist/src/services/notifications/notification.service.test.d.ts +1 -0
  264. package/dist/src/services/notifications/notification.service.test.js +87 -0
  265. package/dist/src/services/workspace/workspace-analytics.service.d.ts +24 -0
  266. package/dist/src/services/workspace/workspace-analytics.service.js +95 -0
  267. package/dist/src/services/workspace/workspace-kb.service.d.ts +40 -0
  268. package/dist/src/services/workspace/workspace-kb.service.js +184 -0
  269. package/dist/src/services/workspace/workspace.service.d.ts +263 -0
  270. package/dist/src/services/workspace/workspace.service.js +401 -0
  271. package/dist/src/trpc.d.ts +60 -0
  272. package/dist/src/trpc.js +217 -0
  273. package/dist/src/types/index.d.ts +126 -0
  274. package/dist/src/types/index.js +1 -0
  275. package/package.json +8 -9
  276. package/prisma/schema.prisma +3 -4
  277. package/prisma/seed.mjs +5 -2
  278. package/prisma.config.ts +16 -0
  279. package/src/lib/prisma.ts +18 -9
  280. package/src/scripts/purge-deleted-users.ts +1 -3
  281. package/tsconfig.json +3 -0
@@ -0,0 +1,84 @@
1
+ import { BaseService } from '../base.service.js';
2
+ function parseObjectValue(value) {
3
+ if (!value)
4
+ return {};
5
+ if (typeof value === 'object') {
6
+ return value;
7
+ }
8
+ if (typeof value === 'string') {
9
+ try {
10
+ const parsed = JSON.parse(value);
11
+ return typeof parsed === 'object' && parsed ? parsed : {};
12
+ }
13
+ catch {
14
+ return {};
15
+ }
16
+ }
17
+ return {};
18
+ }
19
+ export function mergeWorksheetQuestionProgress(questions, progressRows) {
20
+ const progressByQuestionId = new Map(progressRows.map((row) => [row.worksheetQuestionId, row]));
21
+ return questions.map((question) => {
22
+ const progress = progressByQuestionId.get(question.id);
23
+ const existingMeta = parseObjectValue(question.meta);
24
+ if (!progress) {
25
+ return {
26
+ ...question,
27
+ meta: existingMeta,
28
+ };
29
+ }
30
+ const progressMeta = parseObjectValue(progress.meta);
31
+ return {
32
+ ...question,
33
+ meta: {
34
+ ...existingMeta,
35
+ completed: progress.modified,
36
+ userAnswer: progress.userAnswer,
37
+ completedAt: progress.completedAt,
38
+ userMarkScheme: progressMeta.userMarkScheme,
39
+ },
40
+ };
41
+ });
42
+ }
43
+ export function parseWorksheetGenerationContent(content) {
44
+ const parsedRoot = parseObjectValue(content);
45
+ let payload = parsedRoot;
46
+ if (typeof parsedRoot.last_response === 'string') {
47
+ const nested = parseObjectValue(parsedRoot.last_response);
48
+ if (Object.keys(nested).length > 0) {
49
+ payload = nested;
50
+ }
51
+ }
52
+ const rawProblems = payload.problems ?? payload.questions ?? payload;
53
+ const problems = Array.isArray(rawProblems)
54
+ ? rawProblems.map((problem) => (typeof problem === 'object' && problem ? problem : {}))
55
+ : [];
56
+ return {
57
+ problems,
58
+ title: typeof payload.title === 'string' ? payload.title : undefined,
59
+ description: typeof payload.description === 'string' ? payload.description : undefined,
60
+ estimatedTime: typeof payload.estimatedTime === 'string' ? payload.estimatedTime : undefined,
61
+ };
62
+ }
63
+ export function parseQuestionMeta(meta) {
64
+ return parseObjectValue(meta);
65
+ }
66
+ /**
67
+ * Class wrapper around the pure worksheet-content helpers so consumers can
68
+ * depend on a service the same way they depend on other domain services.
69
+ * The standalone function exports remain part of the public surface.
70
+ */
71
+ export class WorksheetContentService extends BaseService {
72
+ constructor(db) {
73
+ super(db);
74
+ }
75
+ mergeProgress(questions, progress) {
76
+ return mergeWorksheetQuestionProgress(questions, progress);
77
+ }
78
+ parseGenerationContent(content) {
79
+ return parseWorksheetGenerationContent(content);
80
+ }
81
+ parseQuestionMeta(meta) {
82
+ return parseQuestionMeta(meta);
83
+ }
84
+ }
@@ -0,0 +1,69 @@
1
+ import { test } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { mergeWorksheetQuestionProgress, parseQuestionMeta, parseWorksheetGenerationContent, } from './worksheet-content.service.js';
4
+ test('parseQuestionMeta: handles null and object inputs', () => {
5
+ assert.deepEqual(parseQuestionMeta(null), {});
6
+ assert.deepEqual(parseQuestionMeta({ markScheme: { points: [] } }), {
7
+ markScheme: { points: [] },
8
+ });
9
+ });
10
+ test('parseQuestionMeta: parses JSON strings', () => {
11
+ assert.deepEqual(parseQuestionMeta('{"options":["A","B"]}'), { options: ['A', 'B'] });
12
+ });
13
+ test('parseQuestionMeta: returns empty object on invalid JSON', () => {
14
+ assert.deepEqual(parseQuestionMeta('not json'), {});
15
+ });
16
+ test('mergeWorksheetQuestionProgress: merges per-user progress into question meta', () => {
17
+ const questions = [
18
+ { id: 'q1', meta: { options: ['A', 'B'] } },
19
+ { id: 'q2', meta: null },
20
+ ];
21
+ const progress = [
22
+ {
23
+ worksheetQuestionId: 'q1',
24
+ modified: true,
25
+ userAnswer: 'A',
26
+ completedAt: new Date('2024-01-01'),
27
+ meta: { userMarkScheme: { points: [] } },
28
+ },
29
+ ];
30
+ const merged = mergeWorksheetQuestionProgress(questions, progress);
31
+ assert.equal(merged[0].meta.options?.[0], 'A');
32
+ assert.equal(merged[0].meta.completed, true);
33
+ assert.equal(merged[0].meta.userAnswer, 'A');
34
+ assert.ok(merged[0].meta.userMarkScheme);
35
+ assert.equal(merged[1].meta.completed, undefined);
36
+ });
37
+ test('parseWorksheetGenerationContent: unwraps last_response strings', () => {
38
+ const content = JSON.stringify({
39
+ last_response: JSON.stringify({
40
+ title: 'Test Title',
41
+ description: 'A description',
42
+ estimatedTime: '15m',
43
+ problems: [{ question: 'What?' }],
44
+ }),
45
+ });
46
+ const parsed = parseWorksheetGenerationContent(content);
47
+ assert.equal(parsed.title, 'Test Title');
48
+ assert.equal(parsed.description, 'A description');
49
+ assert.equal(parsed.estimatedTime, '15m');
50
+ assert.equal(parsed.problems.length, 1);
51
+ });
52
+ test('parseWorksheetGenerationContent: accepts top-level problems', () => {
53
+ const content = JSON.stringify({
54
+ title: 'Plain',
55
+ problems: [{ question: 'Q1' }, { question: 'Q2' }],
56
+ });
57
+ const parsed = parseWorksheetGenerationContent(content);
58
+ assert.equal(parsed.title, 'Plain');
59
+ assert.equal(parsed.problems.length, 2);
60
+ });
61
+ test('parseWorksheetGenerationContent: falls back to questions array', () => {
62
+ const content = JSON.stringify({ questions: [{ question: 'Q' }] });
63
+ assert.equal(parseWorksheetGenerationContent(content).problems.length, 1);
64
+ });
65
+ test('parseWorksheetGenerationContent: returns empty problems on invalid content', () => {
66
+ const parsed = parseWorksheetGenerationContent('not json');
67
+ assert.deepEqual(parsed.problems, []);
68
+ assert.equal(parsed.title, undefined);
69
+ });
@@ -0,0 +1,91 @@
1
+ import { z } from 'zod';
2
+ /** Persisted on Artifact.worksheetConfig and inside generatingMetadata while generating */
3
+ export declare const worksheetModeSchema: z.ZodEnum<{
4
+ practice: "practice";
5
+ quiz: "quiz";
6
+ }>;
7
+ export type WorksheetMode = z.infer<typeof worksheetModeSchema>;
8
+ export declare const worksheetDifficultyInputSchema: z.ZodEnum<{
9
+ easy: "easy";
10
+ medium: "medium";
11
+ hard: "hard";
12
+ }>;
13
+ export type WorksheetDifficultyInput = z.infer<typeof worksheetDifficultyInputSchema>;
14
+ export declare const questionTypeSchema: z.ZodEnum<{
15
+ MULTIPLE_CHOICE: "MULTIPLE_CHOICE";
16
+ TEXT: "TEXT";
17
+ NUMERIC: "NUMERIC";
18
+ TRUE_FALSE: "TRUE_FALSE";
19
+ MATCHING: "MATCHING";
20
+ FILL_IN_THE_BLANK: "FILL_IN_THE_BLANK";
21
+ }>;
22
+ export type WorksheetQuestionTypeConfig = z.infer<typeof questionTypeSchema>;
23
+ /** Shape stored in WorksheetPreset.config and used for generation */
24
+ export declare const worksheetPresetConfigSchema: z.ZodObject<{
25
+ mode: z.ZodDefault<z.ZodEnum<{
26
+ practice: "practice";
27
+ quiz: "quiz";
28
+ }>>;
29
+ numQuestions: z.ZodDefault<z.ZodNumber>;
30
+ difficulty: z.ZodDefault<z.ZodEnum<{
31
+ easy: "easy";
32
+ medium: "medium";
33
+ hard: "hard";
34
+ }>>;
35
+ questionTypes: z.ZodOptional<z.ZodArray<z.ZodEnum<{
36
+ MULTIPLE_CHOICE: "MULTIPLE_CHOICE";
37
+ TEXT: "TEXT";
38
+ NUMERIC: "NUMERIC";
39
+ TRUE_FALSE: "TRUE_FALSE";
40
+ MATCHING: "MATCHING";
41
+ FILL_IN_THE_BLANK: "FILL_IN_THE_BLANK";
42
+ }>>>;
43
+ mcqRatio: z.ZodOptional<z.ZodNumber>;
44
+ }, z.core.$strip>;
45
+ export type WorksheetPresetConfig = z.infer<typeof worksheetPresetConfigSchema>;
46
+ export declare const worksheetPresetConfigPartialSchema: z.ZodObject<{
47
+ mode: z.ZodOptional<z.ZodDefault<z.ZodEnum<{
48
+ practice: "practice";
49
+ quiz: "quiz";
50
+ }>>>;
51
+ numQuestions: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
52
+ difficulty: z.ZodOptional<z.ZodDefault<z.ZodEnum<{
53
+ easy: "easy";
54
+ medium: "medium";
55
+ hard: "hard";
56
+ }>>>;
57
+ questionTypes: z.ZodOptional<z.ZodOptional<z.ZodArray<z.ZodEnum<{
58
+ MULTIPLE_CHOICE: "MULTIPLE_CHOICE";
59
+ TEXT: "TEXT";
60
+ NUMERIC: "NUMERIC";
61
+ TRUE_FALSE: "TRUE_FALSE";
62
+ MATCHING: "MATCHING";
63
+ FILL_IN_THE_BLANK: "FILL_IN_THE_BLANK";
64
+ }>>>>;
65
+ mcqRatio: z.ZodOptional<z.ZodOptional<z.ZodNumber>>;
66
+ }, z.core.$strip>;
67
+ /** Resolved config used by generateFromPrompt background job */
68
+ export type ResolvedWorksheetGeneration = WorksheetPresetConfig & {
69
+ prompt: string;
70
+ title?: string;
71
+ estimatedTime?: string;
72
+ presetId?: string | null;
73
+ };
74
+ export declare function parsePresetConfig(raw: unknown): WorksheetPresetConfig;
75
+ export declare function mergeWorksheetGenerationConfig(presetConfig: Partial<WorksheetPresetConfig> | null | undefined, configOverride: Partial<WorksheetPresetConfig> | null | undefined, legacy: {
76
+ numQuestions?: number;
77
+ difficulty?: WorksheetDifficultyInput;
78
+ mode?: WorksheetMode;
79
+ }): WorksheetPresetConfig;
80
+ /** Normalize one LLM problem row before DB insert */
81
+ export declare function normalizeWorksheetProblemForDb(problem: Record<string, unknown>, index0: number, difficultyUpper: 'EASY' | 'MEDIUM' | 'HARD', forceMcq: boolean): {
82
+ prompt: string;
83
+ answer: string | null;
84
+ type: string;
85
+ difficulty: 'EASY' | 'MEDIUM' | 'HARD';
86
+ order: number;
87
+ meta: {
88
+ options?: string[];
89
+ markScheme?: unknown;
90
+ };
91
+ };
@@ -0,0 +1,95 @@
1
+ import { z } from 'zod';
2
+ /** Persisted on Artifact.worksheetConfig and inside generatingMetadata while generating */
3
+ export const worksheetModeSchema = z.enum(['practice', 'quiz']);
4
+ export const worksheetDifficultyInputSchema = z.enum(['easy', 'medium', 'hard']);
5
+ export const questionTypeSchema = z.enum([
6
+ 'MULTIPLE_CHOICE',
7
+ 'TEXT',
8
+ 'NUMERIC',
9
+ 'TRUE_FALSE',
10
+ 'MATCHING',
11
+ 'FILL_IN_THE_BLANK',
12
+ ]);
13
+ /** Shape stored in WorksheetPreset.config and used for generation */
14
+ export const worksheetPresetConfigSchema = z.object({
15
+ mode: worksheetModeSchema.default('practice'),
16
+ numQuestions: z.number().int().min(1).max(20).default(8),
17
+ difficulty: worksheetDifficultyInputSchema.default('medium'),
18
+ /** When omitted, quiz mode implies MCQ-only on the inference side */
19
+ questionTypes: z.array(questionTypeSchema).optional(),
20
+ /** 0–1 fraction of problems that should be MCQ (practice mode); ignored when quiz forces all MCQ */
21
+ mcqRatio: z.number().min(0).max(1).optional(),
22
+ });
23
+ export const worksheetPresetConfigPartialSchema = worksheetPresetConfigSchema.partial();
24
+ const defaultConfig = worksheetPresetConfigSchema.parse({});
25
+ export function parsePresetConfig(raw) {
26
+ return worksheetPresetConfigSchema.parse(raw);
27
+ }
28
+ export function mergeWorksheetGenerationConfig(presetConfig, configOverride, legacy) {
29
+ // Precedence: legacy flat fields > configOverride > preset > defaults
30
+ // We place legacy AFTER configOverride because configOverride's Zod partial schema injects defaults (like medium)
31
+ // that shouldn't overwrite explicit explicit user choices passing via legacy parameters.
32
+ const merged = {
33
+ ...defaultConfig,
34
+ ...presetConfig,
35
+ ...configOverride,
36
+ ...(legacy.numQuestions !== undefined ? { numQuestions: legacy.numQuestions } : {}),
37
+ ...(legacy.difficulty !== undefined ? { difficulty: legacy.difficulty } : {}),
38
+ ...(legacy.mode !== undefined ? { mode: legacy.mode } : {}),
39
+ };
40
+ return worksheetPresetConfigSchema.parse(merged);
41
+ }
42
+ /** Normalize one LLM problem row before DB insert */
43
+ export function normalizeWorksheetProblemForDb(problem, index0, difficultyUpper, forceMcq) {
44
+ const prompt = (typeof problem.question === 'string' && problem.question) ||
45
+ (typeof problem.prompt === 'string' && problem.prompt) ||
46
+ `Question ${index0 + 1}`;
47
+ let type = String(problem.type || 'TEXT').toUpperCase().replace(/[\s-]/g, '_');
48
+ if (forceMcq)
49
+ type = 'MULTIPLE_CHOICE';
50
+ let options = [];
51
+ if (Array.isArray(problem.options)) {
52
+ options = problem.options.filter((o) => typeof o === 'string' && o.trim().length > 0);
53
+ }
54
+ let answer = (typeof problem.answer === 'string' && problem.answer) ||
55
+ (typeof problem.solution === 'string' && problem.solution) ||
56
+ '';
57
+ if (type === 'MULTIPLE_CHOICE') {
58
+ if (options.length < 2) {
59
+ options = ['Option A', 'Option B', 'Option C', 'Option D'];
60
+ }
61
+ const correctIndex = resolveMcqCorrectIndex(answer, options);
62
+ answer = String(correctIndex);
63
+ }
64
+ return {
65
+ prompt,
66
+ answer: answer || null,
67
+ type,
68
+ difficulty: difficultyUpper,
69
+ order: index0,
70
+ meta: {
71
+ ...(options.length > 0 ? { options } : {}),
72
+ ...(problem.mark_scheme !== undefined ? { markScheme: problem.mark_scheme } : {}),
73
+ // Non-MCQ questions are AI-marked via markScheme; provide a safe fallback
74
+ ...(type === 'TEXT' && problem.mark_scheme === undefined
75
+ ? {
76
+ markScheme: {
77
+ points: [{ point: 1, requirements: 'Typed answer matches expected content.' }],
78
+ totalPoints: 1,
79
+ },
80
+ }
81
+ : {}),
82
+ },
83
+ };
84
+ }
85
+ function resolveMcqCorrectIndex(answer, options) {
86
+ const trimmed = answer.trim();
87
+ const asNum = parseInt(trimmed, 10);
88
+ if (!Number.isNaN(asNum) && asNum >= 0 && asNum < options.length) {
89
+ return asNum;
90
+ }
91
+ const byText = options.findIndex((o) => o.trim() === trimmed);
92
+ if (byText >= 0)
93
+ return byText;
94
+ return 0;
95
+ }
@@ -0,0 +1,20 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { mergeWorksheetGenerationConfig, normalizeWorksheetProblemForDb } from './worksheet-generation.service.js';
4
+ test('mergeWorksheetGenerationConfig: override beats preset and legacy', () => {
5
+ const r = mergeWorksheetGenerationConfig({ mode: 'practice', numQuestions: 5, difficulty: 'easy' }, { mode: 'quiz', numQuestions: 10, difficulty: 'medium' }, { numQuestions: 3, difficulty: 'hard' });
6
+ assert.equal(r.mode, 'quiz');
7
+ assert.equal(r.numQuestions, 3);
8
+ assert.equal(r.difficulty, 'hard');
9
+ });
10
+ test('normalizeWorksheetProblemForDb coerces MCQ answer to option index', () => {
11
+ const row = normalizeWorksheetProblemForDb({
12
+ question: 'Capital of France?',
13
+ answer: 'Paris',
14
+ type: 'MULTIPLE_CHOICE',
15
+ options: ['London', 'Paris', 'Berlin'],
16
+ }, 0, 'MEDIUM', true);
17
+ assert.equal(row.type, 'MULTIPLE_CHOICE');
18
+ assert.equal(row.answer, '1');
19
+ assert.ok(row.meta.options && row.meta.options.length >= 2);
20
+ });