@studious-lms/server 1.5.0 → 1.6.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 (126) hide show
  1. package/dist/index.js +15 -3
  2. package/dist/index.js.map +1 -1
  3. package/dist/lib/config/cors.d.ts.map +1 -1
  4. package/dist/lib/config/cors.js +3 -2
  5. package/dist/lib/config/cors.js.map +1 -1
  6. package/dist/lib/pusher.d.ts +7 -1
  7. package/dist/lib/pusher.d.ts.map +1 -1
  8. package/dist/lib/pusher.js +9 -3
  9. package/dist/lib/pusher.js.map +1 -1
  10. package/dist/models/attendance.d.ts +16 -7
  11. package/dist/models/attendance.d.ts.map +1 -1
  12. package/dist/models/attendance.js +17 -2
  13. package/dist/models/attendance.js.map +1 -1
  14. package/dist/models/conversation.d.ts +17 -17
  15. package/dist/pipelines/aiLabChat.d.ts +20 -9
  16. package/dist/pipelines/aiLabChat.d.ts.map +1 -1
  17. package/dist/pipelines/aiLabChat.js +62 -74
  18. package/dist/pipelines/aiLabChat.js.map +1 -1
  19. package/dist/pipelines/aiNewtonChat.d.ts +13 -4
  20. package/dist/pipelines/aiNewtonChat.d.ts.map +1 -1
  21. package/dist/pipelines/aiNewtonChat.js +39 -27
  22. package/dist/pipelines/aiNewtonChat.js.map +1 -1
  23. package/dist/pipelines/pdfToWorksheet.d.ts +31 -2
  24. package/dist/pipelines/pdfToWorksheet.d.ts.map +1 -1
  25. package/dist/pipelines/pdfToWorksheet.js +160 -28
  26. package/dist/pipelines/pdfToWorksheet.js.map +1 -1
  27. package/dist/routers/_app.d.ts +18 -4
  28. package/dist/routers/_app.d.ts.map +1 -1
  29. package/dist/routers/assignment.d.ts.map +1 -1
  30. package/dist/routers/assignment.js +64 -4
  31. package/dist/routers/assignment.js.map +1 -1
  32. package/dist/routers/labChat.d.ts +1 -0
  33. package/dist/routers/labChat.d.ts.map +1 -1
  34. package/dist/routers/message.d.ts +3 -0
  35. package/dist/routers/message.d.ts.map +1 -1
  36. package/dist/routers/newtonChat.d.ts +1 -0
  37. package/dist/routers/newtonChat.d.ts.map +1 -1
  38. package/dist/routers/worksheet.d.ts +4 -2
  39. package/dist/routers/worksheet.d.ts.map +1 -1
  40. package/dist/routers/worksheet.js +16 -6
  41. package/dist/routers/worksheet.js.map +1 -1
  42. package/dist/services/announcement.js +4 -4
  43. package/dist/services/announcement.js.map +1 -1
  44. package/dist/services/assignment.d.ts.map +1 -1
  45. package/dist/services/assignment.js +16 -6
  46. package/dist/services/assignment.js.map +1 -1
  47. package/dist/services/attendance.d.ts.map +1 -1
  48. package/dist/services/attendance.js +68 -4
  49. package/dist/services/attendance.js.map +1 -1
  50. package/dist/services/labChat.d.ts +8 -1
  51. package/dist/services/labChat.d.ts.map +1 -1
  52. package/dist/services/labChat.js +101 -42
  53. package/dist/services/labChat.js.map +1 -1
  54. package/dist/services/message.d.ts +3 -0
  55. package/dist/services/message.d.ts.map +1 -1
  56. package/dist/services/message.js +8 -2
  57. package/dist/services/message.js.map +1 -1
  58. package/dist/services/newtonChat.d.ts +1 -0
  59. package/dist/services/newtonChat.d.ts.map +1 -1
  60. package/dist/services/newtonChat.js +40 -17
  61. package/dist/services/newtonChat.js.map +1 -1
  62. package/dist/services/worksheet.d.ts +15 -6
  63. package/dist/services/worksheet.d.ts.map +1 -1
  64. package/dist/services/worksheet.js +58 -12
  65. package/dist/services/worksheet.js.map +1 -1
  66. package/dist/utils/inference.d.ts +50 -1
  67. package/dist/utils/inference.d.ts.map +1 -1
  68. package/dist/utils/inference.js +167 -2
  69. package/dist/utils/inference.js.map +1 -1
  70. package/dist/workers/index.js +13 -3
  71. package/dist/workers/index.js.map +1 -1
  72. package/dist/workers/processors/aiLabChat.d.ts +7 -0
  73. package/dist/workers/processors/aiLabChat.d.ts.map +1 -0
  74. package/dist/workers/processors/aiLabChat.js +52 -0
  75. package/dist/workers/processors/aiLabChat.js.map +1 -0
  76. package/dist/workers/processors/aiNewtonChat.d.ts +7 -0
  77. package/dist/workers/processors/aiNewtonChat.d.ts.map +1 -0
  78. package/dist/workers/processors/aiNewtonChat.js +57 -0
  79. package/dist/workers/processors/aiNewtonChat.js.map +1 -0
  80. package/dist/workers/processors/createRunJobWorker.d.ts +46 -0
  81. package/dist/workers/processors/createRunJobWorker.d.ts.map +1 -0
  82. package/dist/workers/processors/createRunJobWorker.js +118 -0
  83. package/dist/workers/processors/createRunJobWorker.js.map +1 -0
  84. package/dist/workers/processors/gradeWorksheet.d.ts +6 -0
  85. package/dist/workers/processors/gradeWorksheet.d.ts.map +1 -0
  86. package/dist/workers/processors/gradeWorksheet.js +19 -0
  87. package/dist/workers/processors/gradeWorksheet.js.map +1 -0
  88. package/dist/workers/processors/pdfToWorksheet.d.ts +5 -6
  89. package/dist/workers/processors/pdfToWorksheet.d.ts.map +1 -1
  90. package/dist/workers/processors/pdfToWorksheet.js +15 -95
  91. package/dist/workers/processors/pdfToWorksheet.js.map +1 -1
  92. package/dist/workers/processors/studentProgressChat.d.ts +8 -0
  93. package/dist/workers/processors/studentProgressChat.d.ts.map +1 -0
  94. package/dist/workers/processors/studentProgressChat.js +27 -0
  95. package/dist/workers/processors/studentProgressChat.js.map +1 -0
  96. package/dist/workers/queues.d.ts +20 -4
  97. package/dist/workers/queues.d.ts.map +1 -1
  98. package/dist/workers/queues.js +3 -3
  99. package/dist/workers/queues.js.map +1 -1
  100. package/package.json +3 -3
  101. package/src/index.ts +12 -1
  102. package/src/lib/config/cors.ts +1 -0
  103. package/src/lib/pusher.ts +7 -1
  104. package/src/models/attendance.ts +15 -0
  105. package/src/pipelines/aiLabChat.ts +70 -78
  106. package/src/pipelines/aiNewtonChat.ts +47 -31
  107. package/src/pipelines/pdfToWorksheet.ts +224 -32
  108. package/src/routers/assignment.ts +74 -3
  109. package/src/routers/worksheet.ts +25 -4
  110. package/src/services/announcement.ts +2 -2
  111. package/src/services/assignment.ts +18 -4
  112. package/src/services/attendance.ts +87 -2
  113. package/src/services/labChat.ts +107 -47
  114. package/src/services/message.ts +6 -0
  115. package/src/services/newtonChat.ts +46 -25
  116. package/src/services/worksheet.ts +71 -10
  117. package/src/utils/inference.ts +209 -1
  118. package/src/workers/index.ts +11 -1
  119. package/src/workers/processors/aiLabChat.ts +65 -0
  120. package/src/workers/processors/aiNewtonChat.ts +75 -0
  121. package/src/workers/processors/createRunJobWorker.ts +164 -0
  122. package/src/workers/processors/gradeWorksheet.ts +16 -0
  123. package/src/workers/processors/pdfToWorksheet.ts +12 -114
  124. package/src/workers/processors/studentProgressChat.ts +28 -0
  125. package/src/workers/queues.ts +20 -5
  126. package/tests/integration/routers/attendance.test.ts +60 -1
@@ -0,0 +1,52 @@
1
+
2
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="cb84c0dd-1443-5364-811e-02e333b3e8c2")}catch(e){}}();
3
+ import { generateAndSendLabIntroduction, generateAndSendLabResponse, } from '../../pipelines/aiLabChat.js';
4
+ import { prisma } from '../../lib/prisma.js';
5
+ import { isAIUser } from '../../utils/aiUser.js';
6
+ import { createRunJobWorker } from './createRunJobWorker.js';
7
+ export const aiLabChatWorker = createRunJobWorker({
8
+ type: 'AI_LAB_CHAT',
9
+ workerOptions: {
10
+ // Lab responses can produce PDFs and large schemas; cap concurrency to
11
+ // keep memory under control and to honour upstream model rate limits.
12
+ concurrency: 1,
13
+ limiter: { max: 5, duration: 60000 },
14
+ },
15
+ run: async ({ conversationId, aiMessageId }) => {
16
+ const labChat = await prisma.labChat.findFirst({
17
+ where: { conversationId },
18
+ select: {
19
+ id: true,
20
+ classId: true,
21
+ context: true,
22
+ class: { select: { subject: true } },
23
+ },
24
+ });
25
+ if (!labChat) {
26
+ throw new Error(`LabChat for conversationId=${conversationId} not found`);
27
+ }
28
+ // The AI message is a placeholder we created before enqueue; the
29
+ // pipeline fills it in. Look up the most recent non-AI message in the
30
+ // conversation to feed the response generator. If none exists, this is
31
+ // the introductory message at the top of a brand-new lab chat.
32
+ const triggeringMessage = await prisma.message.findFirst({
33
+ where: {
34
+ conversationId,
35
+ senderId: { not: 'AI_ASSISTANT' },
36
+ },
37
+ orderBy: { createdAt: 'desc' },
38
+ select: { id: true, content: true, senderId: true },
39
+ });
40
+ if (!triggeringMessage || isAIUser(triggeringMessage.senderId)) {
41
+ await generateAndSendLabIntroduction(labChat.id, conversationId, labChat.context, labChat.class?.subject || 'Lab', aiMessageId);
42
+ return { labChatId: labChat.id, kind: 'introduction', aiMessageId };
43
+ }
44
+ await generateAndSendLabResponse(labChat.id, triggeringMessage.content, {
45
+ classId: labChat.classId,
46
+ aiMessageId,
47
+ });
48
+ return { labChatId: labChat.id, aiMessageId };
49
+ },
50
+ });
51
+ //# sourceMappingURL=aiLabChat.js.map
52
+ //# debugId=cb84c0dd-1443-5364-811e-02e333b3e8c2
@@ -0,0 +1 @@
1
+ {"version":3,"file":"aiLabChat.js","sources":["workers/processors/aiLabChat.ts"],"sourceRoot":"/","sourcesContent":["import {\n generateAndSendLabIntroduction,\n generateAndSendLabResponse,\n} from '../../pipelines/aiLabChat.js';\nimport { prisma } from '../../lib/prisma.js';\nimport { isAIUser } from '../../utils/aiUser.js';\nimport { createRunJobWorker } from './createRunJobWorker.js';\n\nexport const aiLabChatWorker = createRunJobWorker({\n type: 'AI_LAB_CHAT',\n workerOptions: {\n // Lab responses can produce PDFs and large schemas; cap concurrency to\n // keep memory under control and to honour upstream model rate limits.\n concurrency: 1,\n limiter: { max: 5, duration: 60_000 },\n },\n run: async ({ conversationId, aiMessageId }) => {\n const labChat = await prisma.labChat.findFirst({\n where: { conversationId },\n select: {\n id: true,\n classId: true,\n context: true,\n class: { select: { subject: true } },\n },\n });\n\n if (!labChat) {\n throw new Error(\n `LabChat for conversationId=${conversationId} not found`,\n );\n }\n\n // The AI message is a placeholder we created before enqueue; the\n // pipeline fills it in. Look up the most recent non-AI message in the\n // conversation to feed the response generator. If none exists, this is\n // the introductory message at the top of a brand-new lab chat.\n const triggeringMessage = await prisma.message.findFirst({\n where: {\n conversationId,\n senderId: { not: 'AI_ASSISTANT' },\n },\n orderBy: { createdAt: 'desc' },\n select: { id: true, content: true, senderId: true },\n });\n\n if (!triggeringMessage || isAIUser(triggeringMessage.senderId)) {\n await generateAndSendLabIntroduction(\n labChat.id,\n conversationId,\n labChat.context,\n labChat.class?.subject || 'Lab',\n aiMessageId,\n );\n return { labChatId: labChat.id, kind: 'introduction', aiMessageId };\n }\n\n await generateAndSendLabResponse(labChat.id, triggeringMessage.content, {\n classId: labChat.classId,\n aiMessageId,\n });\n\n return { labChatId: labChat.id, aiMessageId };\n },\n});\n"],"names":[],"mappings":";;AAAA,OAAO,EACL,8BAA8B,EAC9B,0BAA0B,GAC3B,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAC7C,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACjD,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAE7D,MAAM,CAAC,MAAM,eAAe,GAAG,kBAAkB,CAAC;IAChD,IAAI,EAAE,aAAa;IACnB,aAAa,EAAE;QACb,uEAAuE;QACvE,sEAAsE;QACtE,WAAW,EAAE,CAAC;QACd,OAAO,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,QAAQ,EAAE,KAAM,EAAE;KACtC;IACD,GAAG,EAAE,KAAK,EAAE,EAAE,cAAc,EAAE,WAAW,EAAE,EAAE,EAAE;QAC7C,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC;YAC7C,KAAK,EAAE,EAAE,cAAc,EAAE;YACzB,MAAM,EAAE;gBACN,EAAE,EAAE,IAAI;gBACR,OAAO,EAAE,IAAI;gBACb,OAAO,EAAE,IAAI;gBACb,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE;aACrC;SACF,CAAC,CAAC;QAEH,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,IAAI,KAAK,CACb,8BAA8B,cAAc,YAAY,CACzD,CAAC;QACJ,CAAC;QAED,iEAAiE;QACjE,sEAAsE;QACtE,uEAAuE;QACvE,+DAA+D;QAC/D,MAAM,iBAAiB,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC;YACvD,KAAK,EAAE;gBACL,cAAc;gBACd,QAAQ,EAAE,EAAE,GAAG,EAAE,cAAc,EAAE;aAClC;YACD,OAAO,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE;YAC9B,MAAM,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE;SACpD,CAAC,CAAC;QAEH,IAAI,CAAC,iBAAiB,IAAI,QAAQ,CAAC,iBAAiB,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC/D,MAAM,8BAA8B,CAClC,OAAO,CAAC,EAAE,EACV,cAAc,EACd,OAAO,CAAC,OAAO,EACf,OAAO,CAAC,KAAK,EAAE,OAAO,IAAI,KAAK,EAC/B,WAAW,CACZ,CAAC;YACF,OAAO,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,EAAE,IAAI,EAAE,cAAc,EAAE,WAAW,EAAE,CAAC;QACtE,CAAC;QAED,MAAM,0BAA0B,CAAC,OAAO,CAAC,EAAE,EAAE,iBAAiB,CAAC,OAAO,EAAE;YACtE,OAAO,EAAE,OAAO,CAAC,OAAO;YACxB,WAAW;SACZ,CAAC,CAAC;QAEH,OAAO,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,EAAE,WAAW,EAAE,CAAC;IAChD,CAAC;CACF,CAAC,CAAC","debug_id":"cb84c0dd-1443-5364-811e-02e333b3e8c2"}
@@ -0,0 +1,7 @@
1
+ export declare const aiNewtonChatWorker: import("bullmq").Worker<{
2
+ conversationId: string;
3
+ aiMessageId: string;
4
+ } & {
5
+ runJobId: string;
6
+ }, any, string>;
7
+ //# sourceMappingURL=aiNewtonChat.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"aiNewtonChat.d.ts","sourceRoot":"/","sources":["workers/processors/aiNewtonChat.ts"],"names":[],"mappings":"AAQA,eAAO,MAAM,kBAAkB;;;;;eAkE7B,CAAC"}
@@ -0,0 +1,57 @@
1
+
2
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="e55f6275-3148-5423-9eae-9460ac8c1ece")}catch(e){}}();
3
+ import { generateAndSendNewtonIntroduction, generateAndSendNewtonResponse, } from '../../pipelines/aiNewtonChat.js';
4
+ import { prisma } from '../../lib/prisma.js';
5
+ import { isAIUser } from '../../utils/aiUser.js';
6
+ import { createRunJobWorker } from './createRunJobWorker.js';
7
+ export const aiNewtonChatWorker = createRunJobWorker({
8
+ type: 'AI_NEWTON_CHAT',
9
+ workerOptions: {
10
+ concurrency: 2,
11
+ limiter: { max: 10, duration: 60000 },
12
+ },
13
+ run: async ({ conversationId, aiMessageId }) => {
14
+ const newtonChat = await prisma.newtonChat.findFirst({
15
+ where: { conversationId },
16
+ select: {
17
+ id: true,
18
+ submission: {
19
+ select: {
20
+ id: true,
21
+ assignment: {
22
+ select: {
23
+ id: true,
24
+ title: true,
25
+ instructions: true,
26
+ class: { select: { subject: true } },
27
+ },
28
+ },
29
+ },
30
+ },
31
+ },
32
+ });
33
+ if (!newtonChat) {
34
+ throw new Error(`NewtonChat for conversationId=${conversationId} not found`);
35
+ }
36
+ // Resolve the triggering student message (if any). On a brand-new
37
+ // tutor session there are no student messages yet, so we run the
38
+ // introduction path; otherwise we run the response path against the
39
+ // most recent non-AI message.
40
+ const triggeringMessage = await prisma.message.findFirst({
41
+ where: {
42
+ conversationId,
43
+ senderId: { not: 'AI_ASSISTANT' },
44
+ },
45
+ orderBy: { createdAt: 'desc' },
46
+ select: { id: true, content: true, senderId: true },
47
+ });
48
+ if (!triggeringMessage || isAIUser(triggeringMessage.senderId)) {
49
+ await generateAndSendNewtonIntroduction(newtonChat.id, conversationId, newtonChat.submission.id, aiMessageId);
50
+ return { newtonChatId: newtonChat.id, kind: 'introduction', aiMessageId };
51
+ }
52
+ await generateAndSendNewtonResponse(newtonChat.id, triggeringMessage.content, conversationId, newtonChat.submission, aiMessageId);
53
+ return { newtonChatId: newtonChat.id, aiMessageId };
54
+ },
55
+ });
56
+ //# sourceMappingURL=aiNewtonChat.js.map
57
+ //# debugId=e55f6275-3148-5423-9eae-9460ac8c1ece
@@ -0,0 +1 @@
1
+ {"version":3,"file":"aiNewtonChat.js","sources":["workers/processors/aiNewtonChat.ts"],"sourceRoot":"/","sourcesContent":["import {\n generateAndSendNewtonIntroduction,\n generateAndSendNewtonResponse,\n} from '../../pipelines/aiNewtonChat.js';\nimport { prisma } from '../../lib/prisma.js';\nimport { isAIUser } from '../../utils/aiUser.js';\nimport { createRunJobWorker } from './createRunJobWorker.js';\n\nexport const aiNewtonChatWorker = createRunJobWorker({\n type: 'AI_NEWTON_CHAT',\n workerOptions: {\n concurrency: 2,\n limiter: { max: 10, duration: 60_000 },\n },\n run: async ({ conversationId, aiMessageId }) => {\n const newtonChat = await prisma.newtonChat.findFirst({\n where: { conversationId },\n select: {\n id: true,\n submission: {\n select: {\n id: true,\n assignment: {\n select: {\n id: true,\n title: true,\n instructions: true,\n class: { select: { subject: true } },\n },\n },\n },\n },\n },\n });\n\n if (!newtonChat) {\n throw new Error(\n `NewtonChat for conversationId=${conversationId} not found`,\n );\n }\n\n // Resolve the triggering student message (if any). On a brand-new\n // tutor session there are no student messages yet, so we run the\n // introduction path; otherwise we run the response path against the\n // most recent non-AI message.\n const triggeringMessage = await prisma.message.findFirst({\n where: {\n conversationId,\n senderId: { not: 'AI_ASSISTANT' },\n },\n orderBy: { createdAt: 'desc' },\n select: { id: true, content: true, senderId: true },\n });\n\n if (!triggeringMessage || isAIUser(triggeringMessage.senderId)) {\n await generateAndSendNewtonIntroduction(\n newtonChat.id,\n conversationId,\n newtonChat.submission.id,\n aiMessageId,\n );\n return { newtonChatId: newtonChat.id, kind: 'introduction', aiMessageId };\n }\n\n await generateAndSendNewtonResponse(\n newtonChat.id,\n triggeringMessage.content,\n conversationId,\n newtonChat.submission,\n aiMessageId,\n );\n\n return { newtonChatId: newtonChat.id, aiMessageId };\n },\n});\n"],"names":[],"mappings":";;AAAA,OAAO,EACL,iCAAiC,EACjC,6BAA6B,GAC9B,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAC7C,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACjD,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAE7D,MAAM,CAAC,MAAM,kBAAkB,GAAG,kBAAkB,CAAC;IACnD,IAAI,EAAE,gBAAgB;IACtB,aAAa,EAAE;QACb,WAAW,EAAE,CAAC;QACd,OAAO,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE,QAAQ,EAAE,KAAM,EAAE;KACvC;IACD,GAAG,EAAE,KAAK,EAAE,EAAE,cAAc,EAAE,WAAW,EAAE,EAAE,EAAE;QAC7C,MAAM,UAAU,GAAG,MAAM,MAAM,CAAC,UAAU,CAAC,SAAS,CAAC;YACnD,KAAK,EAAE,EAAE,cAAc,EAAE;YACzB,MAAM,EAAE;gBACN,EAAE,EAAE,IAAI;gBACR,UAAU,EAAE;oBACV,MAAM,EAAE;wBACN,EAAE,EAAE,IAAI;wBACR,UAAU,EAAE;4BACV,MAAM,EAAE;gCACN,EAAE,EAAE,IAAI;gCACR,KAAK,EAAE,IAAI;gCACX,YAAY,EAAE,IAAI;gCAClB,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE;6BACrC;yBACF;qBACF;iBACF;aACF;SACF,CAAC,CAAC;QAEH,IAAI,CAAC,UAAU,EAAE,CAAC;YAChB,MAAM,IAAI,KAAK,CACb,iCAAiC,cAAc,YAAY,CAC5D,CAAC;QACJ,CAAC;QAED,kEAAkE;QAClE,iEAAiE;QACjE,oEAAoE;QACpE,8BAA8B;QAC9B,MAAM,iBAAiB,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC;YACvD,KAAK,EAAE;gBACL,cAAc;gBACd,QAAQ,EAAE,EAAE,GAAG,EAAE,cAAc,EAAE;aAClC;YACD,OAAO,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE;YAC9B,MAAM,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE;SACpD,CAAC,CAAC;QAEH,IAAI,CAAC,iBAAiB,IAAI,QAAQ,CAAC,iBAAiB,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC/D,MAAM,iCAAiC,CACrC,UAAU,CAAC,EAAE,EACb,cAAc,EACd,UAAU,CAAC,UAAU,CAAC,EAAE,EACxB,WAAW,CACZ,CAAC;YACF,OAAO,EAAE,YAAY,EAAE,UAAU,CAAC,EAAE,EAAE,IAAI,EAAE,cAAc,EAAE,WAAW,EAAE,CAAC;QAC5E,CAAC;QAED,MAAM,6BAA6B,CACjC,UAAU,CAAC,EAAE,EACb,iBAAiB,CAAC,OAAO,EACzB,cAAc,EACd,UAAU,CAAC,UAAU,EACrB,WAAW,CACZ,CAAC;QAEF,OAAO,EAAE,YAAY,EAAE,UAAU,CAAC,EAAE,EAAE,WAAW,EAAE,CAAC;IACtD,CAAC;CACF,CAAC,CAAC","debug_id":"e55f6275-3148-5423-9eae-9460ac8c1ece"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * `createRunJobWorker` — shared factory for every BullMQ processor in this app.
3
+ *
4
+ * Wraps the boilerplate that every RunJob-backed worker needs:
5
+ * - Idempotent skip if the RunJob already SUCCEEDED (recovery after a crash
6
+ * that lost the BullMQ ack).
7
+ * - Status transitions: PENDING/RUNNING → RUNNING (with attempts++) → SUCCEEDED.
8
+ * - Failure handling: record latest error every attempt, flip to FAILED only
9
+ * on the final retry.
10
+ * - Standardised structured logging on `error` / `failed` / `completed`.
11
+ *
12
+ * A processor implementation only has to provide its `RunJobType`, optional
13
+ * BullMQ options (concurrency, limiter, etc.), and a `run` callback that does
14
+ * the actual work. The callback receives the typed `JobInputs[type]` payload
15
+ * and a context object with `runJobId`, `bullJobId`, `attempt`, plus the
16
+ * `userId` / `classId` from the original `enqueueRunJob` call.
17
+ */
18
+ import { Worker, type WorkerOptions } from 'bullmq';
19
+ import { Prisma, type RunJobType } from '@prisma/client';
20
+ import { type JobInputs } from '../queues.js';
21
+ export interface RunJobContext {
22
+ runJobId: string;
23
+ bullJobId: string | undefined;
24
+ /** 1-indexed attempt number of the current run. */
25
+ attempt: number;
26
+ /** From `EnqueueOptions.userId` on the producer side. */
27
+ userId: string | null;
28
+ /** From `EnqueueOptions.classId` on the producer side. */
29
+ classId: string | null;
30
+ }
31
+ export interface CreateRunJobWorkerOptions<T extends RunJobType> {
32
+ type: T;
33
+ /** BullMQ `WorkerOptions` minus `connection`, which is supplied by the helper. */
34
+ workerOptions?: Omit<WorkerOptions, 'connection'>;
35
+ /**
36
+ * The actual work. Return any JSON-serialisable value (or `null`) — it gets
37
+ * persisted to `RunJob.result` for the client to read back.
38
+ */
39
+ run: (input: JobInputs[T], ctx: RunJobContext) => Promise<Prisma.InputJsonValue | null>;
40
+ }
41
+ type WorkerData<T extends RunJobType> = JobInputs[T] & {
42
+ runJobId: string;
43
+ };
44
+ export declare function createRunJobWorker<T extends RunJobType>({ type, workerOptions, run, }: CreateRunJobWorkerOptions<T>): Worker<WorkerData<T>>;
45
+ export {};
46
+ //# sourceMappingURL=createRunJobWorker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createRunJobWorker.d.ts","sourceRoot":"/","sources":["workers/processors/createRunJobWorker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,EAAE,MAAM,EAAY,KAAK,aAAa,EAAE,MAAM,QAAQ,CAAC;AAC9D,OAAO,EAAE,MAAM,EAAE,KAAK,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAIzD,OAAO,EAAe,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAE3D,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,mDAAmD;IACnD,OAAO,EAAE,MAAM,CAAC;IAChB,yDAAyD;IACzD,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,0DAA0D;IAC1D,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AAED,MAAM,WAAW,yBAAyB,CAAC,CAAC,SAAS,UAAU;IAC7D,IAAI,EAAE,CAAC,CAAC;IACR,kFAAkF;IAClF,aAAa,CAAC,EAAE,IAAI,CAAC,aAAa,EAAE,YAAY,CAAC,CAAC;IAClD;;;OAGG;IACH,GAAG,EAAE,CACH,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC,EACnB,GAAG,EAAE,aAAa,KACf,OAAO,CAAC,MAAM,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;CAC5C;AAED,KAAK,UAAU,CAAC,CAAC,SAAS,UAAU,IAAI,SAAS,CAAC,CAAC,CAAC,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC;AAE5E,wBAAgB,kBAAkB,CAAC,CAAC,SAAS,UAAU,EAAE,EACvD,IAAI,EACJ,aAAa,EACb,GAAG,GACJ,EAAE,yBAAyB,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CA4GtD"}
@@ -0,0 +1,118 @@
1
+ /**
2
+ * `createRunJobWorker` — shared factory for every BullMQ processor in this app.
3
+ *
4
+ * Wraps the boilerplate that every RunJob-backed worker needs:
5
+ * - Idempotent skip if the RunJob already SUCCEEDED (recovery after a crash
6
+ * that lost the BullMQ ack).
7
+ * - Status transitions: PENDING/RUNNING → RUNNING (with attempts++) → SUCCEEDED.
8
+ * - Failure handling: record latest error every attempt, flip to FAILED only
9
+ * on the final retry.
10
+ * - Standardised structured logging on `error` / `failed` / `completed`.
11
+ *
12
+ * A processor implementation only has to provide its `RunJobType`, optional
13
+ * BullMQ options (concurrency, limiter, etc.), and a `run` callback that does
14
+ * the actual work. The callback receives the typed `JobInputs[type]` payload
15
+ * and a context object with `runJobId`, `bullJobId`, `attempt`, plus the
16
+ * `userId` / `classId` from the original `enqueueRunJob` call.
17
+ */
18
+
19
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="55ff9081-193c-5053-9606-34eeebf74f10")}catch(e){}}();
20
+ import { Worker } from 'bullmq';
21
+ import { Prisma } from '@prisma/client';
22
+ import { bullConnection } from '../connection.js';
23
+ import { prisma } from '../../lib/prisma.js';
24
+ import { logger } from '../../utils/logger.js';
25
+ import { QUEUE_NAMES } from '../queues.js';
26
+ export function createRunJobWorker({ type, workerOptions, run, }) {
27
+ const name = QUEUE_NAMES[type];
28
+ const worker = new Worker(name, async (job) => {
29
+ const { runJobId, ...rest } = job.data;
30
+ // The destructured rest is `Omit<JobInputs[T] & { runJobId }, 'runJobId'>`,
31
+ // which TS can't simplify back to `JobInputs[T]` while `T` is generic.
32
+ const input = rest;
33
+ const existing = await prisma.runJob.findUnique({
34
+ where: { id: runJobId },
35
+ select: { status: true, result: true, userId: true, classId: true },
36
+ });
37
+ if (existing?.status === 'SUCCEEDED') {
38
+ logger.info(`${name} RunJob already succeeded; skipping re-run`, {
39
+ runJobId,
40
+ bullJobId: job.id,
41
+ });
42
+ return existing.result;
43
+ }
44
+ await prisma.runJob.update({
45
+ where: { id: runJobId },
46
+ data: {
47
+ status: 'RUNNING',
48
+ startedAt: existing?.status === 'RUNNING' ? undefined : new Date(),
49
+ attempts: { increment: 1 },
50
+ },
51
+ });
52
+ const result = await run(input, {
53
+ runJobId,
54
+ bullJobId: job.id,
55
+ attempt: job.attemptsMade + 1,
56
+ userId: existing?.userId ?? null,
57
+ classId: existing?.classId ?? null,
58
+ });
59
+ await prisma.runJob.update({
60
+ where: { id: runJobId },
61
+ data: {
62
+ status: 'SUCCEEDED',
63
+ finishedAt: new Date(),
64
+ error: null,
65
+ result: result === null ? Prisma.JsonNull : result,
66
+ },
67
+ });
68
+ return result;
69
+ }, { connection: bullConnection, ...workerOptions });
70
+ worker.on('error', (err) => {
71
+ logger.error(`${name} worker error`, {
72
+ error: err.message,
73
+ stack: err.stack,
74
+ });
75
+ });
76
+ worker.on('failed', async (job, err) => {
77
+ if (!job)
78
+ return;
79
+ const runJobId = job.data?.runJobId;
80
+ const isFinalAttempt = job.attemptsMade >= (job.opts.attempts ?? 1);
81
+ logger.error(`${name} job failed`, {
82
+ bullJobId: job.id,
83
+ runJobId,
84
+ attempt: job.attemptsMade,
85
+ finalAttempt: isFinalAttempt,
86
+ error: err.message,
87
+ });
88
+ if (!runJobId)
89
+ return;
90
+ await prisma.runJob
91
+ .update({
92
+ where: { id: runJobId },
93
+ data: {
94
+ error: err.message,
95
+ ...(isFinalAttempt
96
+ ? { status: 'FAILED', finishedAt: new Date() }
97
+ : {}),
98
+ },
99
+ })
100
+ .catch((updateError) => {
101
+ logger.error(`Failed to record ${name} failure on RunJob`, {
102
+ runJobId,
103
+ error: updateError instanceof Error
104
+ ? updateError.message
105
+ : String(updateError),
106
+ });
107
+ });
108
+ });
109
+ worker.on('completed', (job) => {
110
+ logger.info(`${name} job completed`, {
111
+ bullJobId: job.id,
112
+ runJobId: job.data?.runJobId,
113
+ });
114
+ });
115
+ return worker;
116
+ }
117
+ //# sourceMappingURL=createRunJobWorker.js.map
118
+ //# debugId=55ff9081-193c-5053-9606-34eeebf74f10
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createRunJobWorker.js","sources":["workers/processors/createRunJobWorker.ts"],"sourceRoot":"/","sourcesContent":["/**\n * `createRunJobWorker` — shared factory for every BullMQ processor in this app.\n *\n * Wraps the boilerplate that every RunJob-backed worker needs:\n * - Idempotent skip if the RunJob already SUCCEEDED (recovery after a crash\n * that lost the BullMQ ack).\n * - Status transitions: PENDING/RUNNING → RUNNING (with attempts++) → SUCCEEDED.\n * - Failure handling: record latest error every attempt, flip to FAILED only\n * on the final retry.\n * - Standardised structured logging on `error` / `failed` / `completed`.\n *\n * A processor implementation only has to provide its `RunJobType`, optional\n * BullMQ options (concurrency, limiter, etc.), and a `run` callback that does\n * the actual work. The callback receives the typed `JobInputs[type]` payload\n * and a context object with `runJobId`, `bullJobId`, `attempt`, plus the\n * `userId` / `classId` from the original `enqueueRunJob` call.\n */\nimport { Worker, type Job, type WorkerOptions } from 'bullmq';\nimport { Prisma, type RunJobType } from '@prisma/client';\nimport { bullConnection } from '../connection.js';\nimport { prisma } from '../../lib/prisma.js';\nimport { logger } from '../../utils/logger.js';\nimport { QUEUE_NAMES, type JobInputs } from '../queues.js';\n\nexport interface RunJobContext {\n runJobId: string;\n bullJobId: string | undefined;\n /** 1-indexed attempt number of the current run. */\n attempt: number;\n /** From `EnqueueOptions.userId` on the producer side. */\n userId: string | null;\n /** From `EnqueueOptions.classId` on the producer side. */\n classId: string | null;\n}\n\nexport interface CreateRunJobWorkerOptions<T extends RunJobType> {\n type: T;\n /** BullMQ `WorkerOptions` minus `connection`, which is supplied by the helper. */\n workerOptions?: Omit<WorkerOptions, 'connection'>;\n /**\n * The actual work. Return any JSON-serialisable value (or `null`) — it gets\n * persisted to `RunJob.result` for the client to read back.\n */\n run: (\n input: JobInputs[T],\n ctx: RunJobContext,\n ) => Promise<Prisma.InputJsonValue | null>;\n}\n\ntype WorkerData<T extends RunJobType> = JobInputs[T] & { runJobId: string };\n\nexport function createRunJobWorker<T extends RunJobType>({\n type,\n workerOptions,\n run,\n}: CreateRunJobWorkerOptions<T>): Worker<WorkerData<T>> {\n const name = QUEUE_NAMES[type];\n\n const worker = new Worker<WorkerData<T>>(\n name,\n async (job: Job<WorkerData<T>>) => {\n const { runJobId, ...rest } = job.data;\n // The destructured rest is `Omit<JobInputs[T] & { runJobId }, 'runJobId'>`,\n // which TS can't simplify back to `JobInputs[T]` while `T` is generic.\n const input = rest as unknown as JobInputs[T];\n\n const existing = await prisma.runJob.findUnique({\n where: { id: runJobId },\n select: { status: true, result: true, userId: true, classId: true },\n });\n\n if (existing?.status === 'SUCCEEDED') {\n logger.info(`${name} RunJob already succeeded; skipping re-run`, {\n runJobId,\n bullJobId: job.id,\n });\n return existing.result;\n }\n\n await prisma.runJob.update({\n where: { id: runJobId },\n data: {\n status: 'RUNNING',\n startedAt: existing?.status === 'RUNNING' ? undefined : new Date(),\n attempts: { increment: 1 },\n },\n });\n\n const result = await run(input, {\n runJobId,\n bullJobId: job.id,\n attempt: job.attemptsMade + 1,\n userId: existing?.userId ?? null,\n classId: existing?.classId ?? null,\n });\n\n await prisma.runJob.update({\n where: { id: runJobId },\n data: {\n status: 'SUCCEEDED',\n finishedAt: new Date(),\n error: null,\n result: result === null ? Prisma.JsonNull : result,\n },\n });\n\n return result;\n },\n { connection: bullConnection, ...workerOptions },\n );\n\n worker.on('error', (err) => {\n logger.error(`${name} worker error`, {\n error: err.message,\n stack: err.stack,\n });\n });\n\n worker.on('failed', async (job, err) => {\n if (!job) return;\n\n const runJobId = job.data?.runJobId;\n const isFinalAttempt = job.attemptsMade >= (job.opts.attempts ?? 1);\n\n logger.error(`${name} job failed`, {\n bullJobId: job.id,\n runJobId,\n attempt: job.attemptsMade,\n finalAttempt: isFinalAttempt,\n error: err.message,\n });\n\n if (!runJobId) return;\n\n await prisma.runJob\n .update({\n where: { id: runJobId },\n data: {\n error: err.message,\n ...(isFinalAttempt\n ? { status: 'FAILED', finishedAt: new Date() }\n : {}),\n },\n })\n .catch((updateError) => {\n logger.error(`Failed to record ${name} failure on RunJob`, {\n runJobId,\n error:\n updateError instanceof Error\n ? updateError.message\n : String(updateError),\n });\n });\n });\n\n worker.on('completed', (job) => {\n logger.info(`${name} job completed`, {\n bullJobId: job.id,\n runJobId: job.data?.runJobId,\n });\n });\n\n return worker;\n}\n"],"names":[],"mappings":"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","debug_id":"55ff9081-193c-5053-9606-34eeebf74f10"}
@@ -0,0 +1,6 @@
1
+ export declare const gradeWorksheetWorker: import("bullmq").Worker<{
2
+ worksheetResponseId: string;
3
+ } & {
4
+ runJobId: string;
5
+ }, any, string>;
6
+ //# sourceMappingURL=gradeWorksheet.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gradeWorksheet.d.ts","sourceRoot":"/","sources":["workers/processors/gradeWorksheet.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,oBAAoB;;;;eAY/B,CAAC"}
@@ -0,0 +1,19 @@
1
+
2
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="1acdee19-3e17-56ee-ab44-55001b9460f2")}catch(e){}}();
3
+ import { gradeWorksheetPipeline } from '../../pipelines/gradeWorksheet.js';
4
+ import { createRunJobWorker } from './createRunJobWorker.js';
5
+ export const gradeWorksheetWorker = createRunJobWorker({
6
+ type: 'GRADE_WORKSHEET',
7
+ workerOptions: {
8
+ concurrency: 2,
9
+ // Each pipeline run can fan out to many per-question inferences; keep
10
+ // throughput modest until we measure real-world usage.
11
+ limiter: { max: 5, duration: 60000 },
12
+ },
13
+ run: async ({ worksheetResponseId }) => {
14
+ await gradeWorksheetPipeline(worksheetResponseId);
15
+ return { worksheetResponseId };
16
+ },
17
+ });
18
+ //# sourceMappingURL=gradeWorksheet.js.map
19
+ //# debugId=1acdee19-3e17-56ee-ab44-55001b9460f2
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gradeWorksheet.js","sources":["workers/processors/gradeWorksheet.ts"],"sourceRoot":"/","sourcesContent":["import { gradeWorksheetPipeline } from '../../pipelines/gradeWorksheet.js';\nimport { createRunJobWorker } from './createRunJobWorker.js';\n\nexport const gradeWorksheetWorker = createRunJobWorker({\n type: 'GRADE_WORKSHEET',\n workerOptions: {\n concurrency: 2,\n // Each pipeline run can fan out to many per-question inferences; keep\n // throughput modest until we measure real-world usage.\n limiter: { max: 5, duration: 60_000 },\n },\n run: async ({ worksheetResponseId }) => {\n await gradeWorksheetPipeline(worksheetResponseId);\n return { worksheetResponseId };\n },\n});\n"],"names":[],"mappings":";;AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,mCAAmC,CAAC;AAC3E,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAE7D,MAAM,CAAC,MAAM,oBAAoB,GAAG,kBAAkB,CAAC;IACrD,IAAI,EAAE,iBAAiB;IACvB,aAAa,EAAE;QACb,WAAW,EAAE,CAAC;QACd,sEAAsE;QACtE,uDAAuD;QACvD,OAAO,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,QAAQ,EAAE,KAAM,EAAE;KACtC;IACD,GAAG,EAAE,KAAK,EAAE,EAAE,mBAAmB,EAAE,EAAE,EAAE;QACrC,MAAM,sBAAsB,CAAC,mBAAmB,CAAC,CAAC;QAClD,OAAO,EAAE,mBAAmB,EAAE,CAAC;IACjC,CAAC;CACF,CAAC,CAAC","debug_id":"1acdee19-3e17-56ee-ab44-55001b9460f2"}
@@ -1,9 +1,8 @@
1
- import { Worker } from 'bullmq';
2
- interface PdfToWorksheetJobData {
3
- runJobId: string;
1
+ export declare const pdfToWorksheetWorker: import("bullmq").Worker<{
4
2
  gcsUrl: string;
5
3
  classId: string;
6
- }
7
- export declare const pdfToWorksheetWorker: Worker<PdfToWorksheetJobData, any, string>;
8
- export {};
4
+ worksheetId: string;
5
+ } & {
6
+ runJobId: string;
7
+ }, any, string>;
9
8
  //# sourceMappingURL=pdfToWorksheet.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"pdfToWorksheet.d.ts","sourceRoot":"/","sources":["workers/processors/pdfToWorksheet.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAY,MAAM,QAAQ,CAAC;AAO1C,UAAU,qBAAqB;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB;AA8CD,eAAO,MAAM,oBAAoB,4CAYhC,CAAC"}
1
+ {"version":3,"file":"pdfToWorksheet.d.ts","sourceRoot":"/","sources":["workers/processors/pdfToWorksheet.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,oBAAoB;;;;;;eAa/B,CAAC"}
@@ -1,100 +1,20 @@
1
1
 
2
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="1b13378b-3952-5f91-b138-a69c3693a56d")}catch(e){}}();
3
- import { Worker } from 'bullmq';
4
- import { Prisma } from '@prisma/client';
5
- import { bullConnection } from '../connection.js';
6
- import { prisma } from '../../lib/prisma.js';
7
- import { logger } from '../../utils/logger.js';
2
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="3beb4788-6c75-5aae-b6c1-3195cbdeffa3")}catch(e){}}();
8
3
  import { pdfToWorksheet } from '../../pipelines/pdfToWorksheet.js';
9
- async function process(job) {
10
- const { runJobId, gcsUrl, classId } = job.data;
11
- // If a previous attempt already succeeded for this RunJob (rare race after a
12
- // crash that lost the BullMQ ack), return the prior result instead of
13
- // re-running the AI call and creating a duplicate worksheet.
14
- const existing = await prisma.runJob.findUnique({
15
- where: { id: runJobId },
16
- select: { status: true, result: true },
17
- });
18
- if (existing?.status === 'SUCCEEDED') {
19
- logger.info('pdfToWorksheet RunJob already succeeded; skipping re-run', {
20
- runJobId,
21
- bullJobId: job.id,
22
- });
23
- return existing.result;
24
- }
25
- await prisma.runJob.update({
26
- where: { id: runJobId },
27
- data: {
28
- status: 'RUNNING',
29
- startedAt: existing?.status === 'RUNNING' ? undefined : new Date(),
30
- attempts: { increment: 1 },
31
- },
32
- });
33
- const worksheet = await pdfToWorksheet(gcsUrl, classId);
34
- const result = worksheet ? { worksheetId: worksheet.id, name: worksheet.name } : null;
35
- await prisma.runJob.update({
36
- where: { id: runJobId },
37
- data: {
38
- status: 'SUCCEEDED',
39
- finishedAt: new Date(),
40
- error: null,
41
- result: result === null ? Prisma.JsonNull : result,
42
- },
43
- });
44
- return result;
45
- }
46
- export const pdfToWorksheetWorker = new Worker('pdfToWorksheet', process, {
47
- connection: bullConnection,
48
- // Conservative defaults — tune once we have real-world numbers.
49
- concurrency: 2,
50
- limiter: {
51
- max: 5,
52
- duration: 60000, // 5 jobs / minute, e.g. to stay under OpenAI rate limits
4
+ import { createRunJobWorker } from './createRunJobWorker.js';
5
+ export const pdfToWorksheetWorker = createRunJobWorker({
6
+ type: 'PDF_TO_WORKSHEET',
7
+ workerOptions: {
8
+ concurrency: 2,
9
+ // 5 jobs / minute, e.g. to stay under OpenAI rate limits.
10
+ limiter: { max: 5, duration: 60000 },
11
+ },
12
+ run: async ({ gcsUrl, classId, worksheetId }) => {
13
+ const worksheet = await pdfToWorksheet(gcsUrl, classId, { worksheetId });
14
+ return worksheet
15
+ ? { worksheetId: worksheet.id, name: worksheet.name }
16
+ : null;
53
17
  },
54
- });
55
- pdfToWorksheetWorker.on('error', (err) => {
56
- logger.error('pdfToWorksheet worker error', {
57
- error: err.message,
58
- stack: err.stack,
59
- });
60
- });
61
- pdfToWorksheetWorker.on('failed', async (job, err) => {
62
- if (!job)
63
- return;
64
- const runJobId = job.data?.runJobId;
65
- const isFinalAttempt = job.attemptsMade >= (job.opts.attempts ?? 1);
66
- logger.error('pdfToWorksheet job failed', {
67
- bullJobId: job.id,
68
- runJobId,
69
- attempt: job.attemptsMade,
70
- finalAttempt: isFinalAttempt,
71
- error: err.message,
72
- });
73
- if (!runJobId)
74
- return;
75
- // Always record the latest error message on the RunJob row.
76
- // Only flip to FAILED on the final attempt — earlier failures are still
77
- // "RUNNING" because BullMQ is going to retry.
78
- await prisma.runJob.update({
79
- where: { id: runJobId },
80
- data: {
81
- error: err.message,
82
- ...(isFinalAttempt
83
- ? { status: 'FAILED', finishedAt: new Date() }
84
- : {}),
85
- },
86
- }).catch((updateError) => {
87
- logger.error('Failed to record pdfToWorksheet failure on RunJob', {
88
- runJobId,
89
- error: updateError instanceof Error ? updateError.message : String(updateError),
90
- });
91
- });
92
- });
93
- pdfToWorksheetWorker.on('completed', (job) => {
94
- logger.info('pdfToWorksheet job completed', {
95
- bullJobId: job.id,
96
- runJobId: job.data?.runJobId,
97
- });
98
18
  });
99
19
  //# sourceMappingURL=pdfToWorksheet.js.map
100
- //# debugId=1b13378b-3952-5f91-b138-a69c3693a56d
20
+ //# debugId=3beb4788-6c75-5aae-b6c1-3195cbdeffa3
@@ -1 +1 @@
1
- {"version":3,"file":"pdfToWorksheet.js","sources":["workers/processors/pdfToWorksheet.ts"],"sourceRoot":"/","sourcesContent":["import { Worker, type Job } from 'bullmq';\nimport { Prisma } from '@prisma/client';\nimport { bullConnection } from '../connection.js';\nimport { prisma } from '../../lib/prisma.js';\nimport { logger } from '../../utils/logger.js';\nimport { pdfToWorksheet } from '../../pipelines/pdfToWorksheet.js';\n\ninterface PdfToWorksheetJobData {\n runJobId: string;\n gcsUrl: string;\n classId: string;\n}\n\nasync function process(job: Job<PdfToWorksheetJobData>) {\n const { runJobId, gcsUrl, classId } = job.data;\n\n // If a previous attempt already succeeded for this RunJob (rare race after a\n // crash that lost the BullMQ ack), return the prior result instead of\n // re-running the AI call and creating a duplicate worksheet.\n const existing = await prisma.runJob.findUnique({\n where: { id: runJobId },\n select: { status: true, result: true },\n });\n if (existing?.status === 'SUCCEEDED') {\n logger.info('pdfToWorksheet RunJob already succeeded; skipping re-run', {\n runJobId,\n bullJobId: job.id,\n });\n return existing.result;\n }\n\n await prisma.runJob.update({\n where: { id: runJobId },\n data: {\n status: 'RUNNING',\n startedAt: existing?.status === 'RUNNING' ? undefined : new Date(),\n attempts: { increment: 1 },\n },\n });\n\n const worksheet = await pdfToWorksheet(gcsUrl, classId);\n\n const result = worksheet ? { worksheetId: worksheet.id, name: worksheet.name } : null;\n\n await prisma.runJob.update({\n where: { id: runJobId },\n data: {\n status: 'SUCCEEDED',\n finishedAt: new Date(),\n error: null,\n result: result === null ? Prisma.JsonNull : (result as Prisma.InputJsonValue),\n },\n });\n\n return result;\n}\n\nexport const pdfToWorksheetWorker = new Worker<PdfToWorksheetJobData>(\n 'pdfToWorksheet',\n process,\n {\n connection: bullConnection,\n // Conservative defaults — tune once we have real-world numbers.\n concurrency: 2,\n limiter: {\n max: 5,\n duration: 60_000, // 5 jobs / minute, e.g. to stay under OpenAI rate limits\n },\n },\n);\n\npdfToWorksheetWorker.on('error', (err) => {\n logger.error('pdfToWorksheet worker error', {\n error: err.message,\n stack: err.stack,\n });\n});\n\npdfToWorksheetWorker.on('failed', async (job, err) => {\n if (!job) return;\n\n const runJobId = job.data?.runJobId;\n const isFinalAttempt = job.attemptsMade >= (job.opts.attempts ?? 1);\n\n logger.error('pdfToWorksheet job failed', {\n bullJobId: job.id,\n runJobId,\n attempt: job.attemptsMade,\n finalAttempt: isFinalAttempt,\n error: err.message,\n });\n\n if (!runJobId) return;\n\n // Always record the latest error message on the RunJob row.\n // Only flip to FAILED on the final attempt — earlier failures are still\n // \"RUNNING\" because BullMQ is going to retry.\n await prisma.runJob.update({\n where: { id: runJobId },\n data: {\n error: err.message,\n ...(isFinalAttempt\n ? { status: 'FAILED', finishedAt: new Date() }\n : {}),\n },\n }).catch((updateError) => {\n logger.error('Failed to record pdfToWorksheet failure on RunJob', {\n runJobId,\n error: updateError instanceof Error ? updateError.message : String(updateError),\n });\n });\n});\n\npdfToWorksheetWorker.on('completed', (job) => {\n logger.info('pdfToWorksheet job completed', {\n bullJobId: job.id,\n runJobId: job.data?.runJobId,\n });\n});\n"],"names":[],"mappings":"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","debug_id":"1b13378b-3952-5f91-b138-a69c3693a56d"}
1
+ {"version":3,"file":"pdfToWorksheet.js","sources":["workers/processors/pdfToWorksheet.ts"],"sourceRoot":"/","sourcesContent":["import { pdfToWorksheet } from '../../pipelines/pdfToWorksheet.js';\nimport { createRunJobWorker } from './createRunJobWorker.js';\n\nexport const pdfToWorksheetWorker = createRunJobWorker({\n type: 'PDF_TO_WORKSHEET',\n workerOptions: {\n concurrency: 2,\n // 5 jobs / minute, e.g. to stay under OpenAI rate limits.\n limiter: { max: 5, duration: 60_000 },\n },\n run: async ({ gcsUrl, classId, worksheetId }) => {\n const worksheet = await pdfToWorksheet(gcsUrl, classId, { worksheetId });\n return worksheet\n ? { worksheetId: worksheet.id, name: worksheet.name }\n : null;\n },\n});\n"],"names":[],"mappings":";;AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;AACnE,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAE7D,MAAM,CAAC,MAAM,oBAAoB,GAAG,kBAAkB,CAAC;IACrD,IAAI,EAAE,kBAAkB;IACxB,aAAa,EAAE;QACb,WAAW,EAAE,CAAC;QACd,0DAA0D;QAC1D,OAAO,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,QAAQ,EAAE,KAAM,EAAE;KACtC;IACD,GAAG,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,EAAE,EAAE,EAAE;QAC9C,MAAM,SAAS,GAAG,MAAM,cAAc,CAAC,MAAM,EAAE,OAAO,EAAE,EAAE,WAAW,EAAE,CAAC,CAAC;QACzE,OAAO,SAAS;YACd,CAAC,CAAC,EAAE,WAAW,EAAE,SAAS,CAAC,EAAE,EAAE,IAAI,EAAE,SAAS,CAAC,IAAI,EAAE;YACrD,CAAC,CAAC,IAAI,CAAC;IACX,CAAC;CACF,CAAC,CAAC","debug_id":"3beb4788-6c75-5aae-b6c1-3195cbdeffa3"}
@@ -0,0 +1,8 @@
1
+ export declare const studentProgressChatWorker: import("bullmq").Worker<{
2
+ classId: string;
3
+ studentId: string;
4
+ message: string;
5
+ } & {
6
+ runJobId: string;
7
+ }, any, string>;
8
+ //# sourceMappingURL=studentProgressChat.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"studentProgressChat.d.ts","sourceRoot":"/","sources":["workers/processors/studentProgressChat.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,yBAAyB;;;;;;eAwBpC,CAAC"}
@@ -0,0 +1,27 @@
1
+
2
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="13740d05-3496-50cc-a32d-baa98cef5e0b")}catch(e){}}();
3
+ import { chatAboutStudentProgress } from '../../services/studentProgress.js';
4
+ import { createRunJobWorker } from './createRunJobWorker.js';
5
+ export const studentProgressChatWorker = createRunJobWorker({
6
+ type: 'STUDENT_PROGRESS_CHAT',
7
+ workerOptions: {
8
+ concurrency: 4,
9
+ limiter: { max: 20, duration: 60000 },
10
+ },
11
+ run: async ({ classId, studentId, message }, ctx) => {
12
+ // `chatAboutStudentProgress` runs an authorization check using the viewer
13
+ // id, so callers MUST pass `userId` via `enqueueRunJob` options for the
14
+ // service to receive it here.
15
+ if (!ctx.userId) {
16
+ throw new Error('STUDENT_PROGRESS_CHAT requires `userId` on the RunJob (set via enqueueRunJob options)');
17
+ }
18
+ const response = await chatAboutStudentProgress(ctx.userId, {
19
+ classId,
20
+ studentId,
21
+ message,
22
+ });
23
+ return response;
24
+ },
25
+ });
26
+ //# sourceMappingURL=studentProgressChat.js.map
27
+ //# debugId=13740d05-3496-50cc-a32d-baa98cef5e0b
@@ -0,0 +1 @@
1
+ {"version":3,"file":"studentProgressChat.js","sources":["workers/processors/studentProgressChat.ts"],"sourceRoot":"/","sourcesContent":["import { chatAboutStudentProgress } from '../../services/studentProgress.js';\nimport { createRunJobWorker } from './createRunJobWorker.js';\n\nexport const studentProgressChatWorker = createRunJobWorker({\n type: 'STUDENT_PROGRESS_CHAT',\n workerOptions: {\n concurrency: 4,\n limiter: { max: 20, duration: 60_000 },\n },\n run: async ({ classId, studentId, message }, ctx) => {\n // `chatAboutStudentProgress` runs an authorization check using the viewer\n // id, so callers MUST pass `userId` via `enqueueRunJob` options for the\n // service to receive it here.\n if (!ctx.userId) {\n throw new Error(\n 'STUDENT_PROGRESS_CHAT requires `userId` on the RunJob (set via enqueueRunJob options)',\n );\n }\n\n const response = await chatAboutStudentProgress(ctx.userId, {\n classId,\n studentId,\n message,\n });\n\n return response;\n },\n});\n"],"names":[],"mappings":";;AAAA,OAAO,EAAE,wBAAwB,EAAE,MAAM,mCAAmC,CAAC;AAC7E,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAE7D,MAAM,CAAC,MAAM,yBAAyB,GAAG,kBAAkB,CAAC;IAC1D,IAAI,EAAE,uBAAuB;IAC7B,aAAa,EAAE;QACb,WAAW,EAAE,CAAC;QACd,OAAO,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE,QAAQ,EAAE,KAAM,EAAE;KACvC;IACD,GAAG,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,EAAE,GAAG,EAAE,EAAE;QAClD,0EAA0E;QAC1E,wEAAwE;QACxE,8BAA8B;QAC9B,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC;YAChB,MAAM,IAAI,KAAK,CACb,uFAAuF,CACxF,CAAC;QACJ,CAAC;QAED,MAAM,QAAQ,GAAG,MAAM,wBAAwB,CAAC,GAAG,CAAC,MAAM,EAAE;YAC1D,OAAO;YACP,SAAS;YACT,OAAO;SACR,CAAC,CAAC;QAEH,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF,CAAC,CAAC","debug_id":"13740d05-3496-50cc-a32d-baa98cef5e0b"}