@studious-lms/server 1.19.0 → 1.20.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 (151) hide show
  1. package/dist/index.js +8 -2
  2. package/dist/index.js.map +1 -1
  3. package/dist/lib/pusher.d.ts +3 -1
  4. package/dist/lib/pusher.d.ts.map +1 -1
  5. package/dist/lib/pusher.js +5 -3
  6. package/dist/lib/pusher.js.map +1 -1
  7. package/dist/middleware/security.d.ts.map +1 -1
  8. package/dist/middleware/security.js +4 -4
  9. package/dist/middleware/security.js.map +1 -1
  10. package/dist/models/class.d.ts +1 -1
  11. package/dist/models/class.d.ts.map +1 -1
  12. package/dist/models/class.js +5 -4
  13. package/dist/models/class.js.map +1 -1
  14. package/dist/models/conversation.d.ts +17 -17
  15. package/dist/models/notification.d.ts +63 -11
  16. package/dist/models/notification.d.ts.map +1 -1
  17. package/dist/models/notification.js +24 -10
  18. package/dist/models/notification.js.map +1 -1
  19. package/dist/pipelines/aiLabChat.d.ts +1 -0
  20. package/dist/pipelines/aiLabChat.d.ts.map +1 -1
  21. package/dist/pipelines/aiLabChat.js +7 -2
  22. package/dist/pipelines/aiLabChat.js.map +1 -1
  23. package/dist/pipelines/aiNewtonChat.d.ts +2 -2
  24. package/dist/pipelines/aiNewtonChat.d.ts.map +1 -1
  25. package/dist/pipelines/aiNewtonChat.js +12 -4
  26. package/dist/pipelines/aiNewtonChat.js.map +1 -1
  27. package/dist/pipelines/gradeWorksheet.d.ts +3 -3
  28. package/dist/pipelines/gradeWorksheet.d.ts.map +1 -1
  29. package/dist/pipelines/gradeWorksheet.js +13 -9
  30. package/dist/pipelines/gradeWorksheet.js.map +1 -1
  31. package/dist/pipelines/pdfToWorksheet.d.ts +2 -0
  32. package/dist/pipelines/pdfToWorksheet.d.ts.map +1 -1
  33. package/dist/pipelines/pdfToWorksheet.js +3 -2
  34. package/dist/pipelines/pdfToWorksheet.js.map +1 -1
  35. package/dist/routers/_app.d.ts +174 -82
  36. package/dist/routers/_app.d.ts.map +1 -1
  37. package/dist/routers/announcement.d.ts.map +1 -1
  38. package/dist/routers/announcement.js +2 -2
  39. package/dist/routers/announcement.js.map +1 -1
  40. package/dist/routers/assignment.d.ts.map +1 -1
  41. package/dist/routers/assignment.js +12 -2
  42. package/dist/routers/assignment.js.map +1 -1
  43. package/dist/routers/notifications.d.ts +51 -41
  44. package/dist/routers/notifications.d.ts.map +1 -1
  45. package/dist/routers/notifications.js +22 -18
  46. package/dist/routers/notifications.js.map +1 -1
  47. package/dist/routers/school.d.ts +30 -0
  48. package/dist/routers/school.d.ts.map +1 -1
  49. package/dist/routers/school.js +11 -2
  50. package/dist/routers/school.js.map +1 -1
  51. package/dist/routers/user.d.ts +12 -0
  52. package/dist/routers/user.d.ts.map +1 -1
  53. package/dist/routers/user.js +11 -2
  54. package/dist/routers/user.js.map +1 -1
  55. package/dist/seedDatabase.d.ts +4 -0
  56. package/dist/seedDatabase.d.ts.map +1 -1
  57. package/dist/services/aiChatStream.d.ts +5 -0
  58. package/dist/services/aiChatStream.d.ts.map +1 -1
  59. package/dist/services/aiChatStream.js +32 -3
  60. package/dist/services/aiChatStream.js.map +1 -1
  61. package/dist/services/aiStream.d.ts +5 -0
  62. package/dist/services/aiStream.d.ts.map +1 -1
  63. package/dist/services/aiStream.js +29 -3
  64. package/dist/services/aiStream.js.map +1 -1
  65. package/dist/services/aiUsage.d.ts +72 -0
  66. package/dist/services/aiUsage.d.ts.map +1 -0
  67. package/dist/services/aiUsage.js +150 -0
  68. package/dist/services/aiUsage.js.map +1 -0
  69. package/dist/services/announcement.d.ts.map +1 -1
  70. package/dist/services/announcement.js +7 -2
  71. package/dist/services/announcement.js.map +1 -1
  72. package/dist/services/assignment.d.ts.map +1 -1
  73. package/dist/services/assignment.js +29 -4
  74. package/dist/services/assignment.js.map +1 -1
  75. package/dist/services/attendance.js +10 -2
  76. package/dist/services/attendance.js.map +1 -1
  77. package/dist/services/class.d.ts.map +1 -1
  78. package/dist/services/class.js +6 -2
  79. package/dist/services/class.js.map +1 -1
  80. package/dist/services/discussion.d.ts.map +1 -1
  81. package/dist/services/discussion.js +41 -12
  82. package/dist/services/discussion.js.map +1 -1
  83. package/dist/services/discussionSummary.d.ts.map +1 -1
  84. package/dist/services/discussionSummary.js +3 -2
  85. package/dist/services/discussionSummary.js.map +1 -1
  86. package/dist/services/notification.d.ts +79 -16
  87. package/dist/services/notification.d.ts.map +1 -1
  88. package/dist/services/notification.js +64 -16
  89. package/dist/services/notification.js.map +1 -1
  90. package/dist/services/studentProgress.d.ts.map +1 -1
  91. package/dist/services/studentProgress.js +6 -2
  92. package/dist/services/studentProgress.js.map +1 -1
  93. package/dist/services/userPrivacy.d.ts +4 -0
  94. package/dist/services/userPrivacy.d.ts.map +1 -1
  95. package/dist/services/userPrivacy.js +6 -2
  96. package/dist/services/userPrivacy.js.map +1 -1
  97. package/dist/services/worksheet.js +4 -4
  98. package/dist/services/worksheet.js.map +1 -1
  99. package/dist/utils/inference.d.ts +27 -0
  100. package/dist/utils/inference.d.ts.map +1 -1
  101. package/dist/utils/inference.js +23 -3
  102. package/dist/utils/inference.js.map +1 -1
  103. package/dist/workers/processors/aiLabChat.d.ts.map +1 -1
  104. package/dist/workers/processors/aiLabChat.js +4 -3
  105. package/dist/workers/processors/aiLabChat.js.map +1 -1
  106. package/dist/workers/processors/aiNewtonChat.d.ts.map +1 -1
  107. package/dist/workers/processors/aiNewtonChat.js +5 -5
  108. package/dist/workers/processors/aiNewtonChat.js.map +1 -1
  109. package/dist/workers/processors/gradeWorksheet.js +4 -4
  110. package/dist/workers/processors/gradeWorksheet.js.map +1 -1
  111. package/dist/workers/processors/pdfToWorksheet.d.ts.map +1 -1
  112. package/dist/workers/processors/pdfToWorksheet.js +7 -4
  113. package/dist/workers/processors/pdfToWorksheet.js.map +1 -1
  114. package/package.json +1 -1
  115. package/prisma/migrations/20260708011500_add_ai_usage_attribution/migration.sql +13 -0
  116. package/prisma/migrations/20260708013000_add_notification_type_and_entity/migration.sql +14 -0
  117. package/prisma/schema.prisma +29 -5
  118. package/src/index.ts +7 -0
  119. package/src/lib/pusher.ts +3 -1
  120. package/src/middleware/security.ts +2 -2
  121. package/src/models/class.ts +3 -2
  122. package/src/models/notification.ts +42 -13
  123. package/src/pipelines/aiLabChat.ts +6 -1
  124. package/src/pipelines/aiNewtonChat.ts +10 -0
  125. package/src/pipelines/gradeWorksheet.ts +25 -7
  126. package/src/pipelines/pdfToWorksheet.ts +3 -0
  127. package/src/routers/announcement.ts +0 -1
  128. package/src/routers/assignment.ts +10 -0
  129. package/src/routers/notifications.ts +26 -24
  130. package/src/routers/school.ts +12 -0
  131. package/src/routers/user.ts +12 -0
  132. package/src/services/aiChatStream.ts +35 -0
  133. package/src/services/aiStream.ts +34 -0
  134. package/src/services/aiUsage.ts +202 -0
  135. package/src/services/announcement.ts +5 -0
  136. package/src/services/assignment.ts +29 -2
  137. package/src/services/attendance.ts +12 -0
  138. package/src/services/class.ts +4 -0
  139. package/src/services/discussion.ts +50 -11
  140. package/src/services/discussionSummary.ts +1 -0
  141. package/src/services/notification.ts +83 -20
  142. package/src/services/studentProgress.ts +4 -0
  143. package/src/services/userPrivacy.ts +4 -0
  144. package/src/services/worksheet.ts +2 -2
  145. package/src/utils/inference.ts +40 -2
  146. package/src/workers/processors/aiLabChat.ts +2 -1
  147. package/src/workers/processors/aiNewtonChat.ts +3 -1
  148. package/src/workers/processors/gradeWorksheet.ts +2 -2
  149. package/src/workers/processors/pdfToWorksheet.ts +5 -2
  150. package/tests/integration/routers/notification.test.ts +68 -20
  151. package/tests/unit/services/aiUsage.test.ts +105 -0
@@ -1 +1 @@
1
- {"version":3,"file":"gradeWorksheet.js","sources":["pipelines/gradeWorksheet.ts"],"sourceRoot":"/","sourcesContent":["/**\n * Grade worksheet pipeline – AI-powered grading of worksheet responses.\n * Grades questions via inference API, broadcasts status via Pusher (pending/completed/failed/cancelled).\n */\nimport { JobStatus, WorksheetQuestionType } from \"@prisma/client\";\nimport { prisma } from \"../lib/prisma.js\";\nimport { logger } from \"../utils/logger.js\";\nimport z from \"zod\";\nimport { inference } from \"../utils/inference.js\";\nimport { getAIUserId } from \"../utils/aiUser.js\";\nimport { pusher, worksheetChannel } from \"../lib/pusher.js\";\n\n\nconst removeAllPreviousAIComments = async (worksheetQuestionProgressId: string) => {\n await prisma.comment.deleteMany({\n where: {\n studentQuestionProgressId: worksheetQuestionProgressId,\n authorId: getAIUserId(),\n },\n });\n};\n\ntype MarkSchemeItem = { id: string; points: number; description?: string; text?: string };\n\nconst gradingResponseSchema = z.object({\n isCorrect: z.boolean(),\n points: z.number(),\n markschemeState: z.array(z.object({\n id: z.string(),\n correct: z.boolean(),\n })),\n comments: z.array(z.string()),\n annotations: z.array(z.object({\n quote: z.string().describe('An exact substring from the student response that is relevant to this criterion.'),\n markSchemeId: z.string().describe('The id of the mark scheme criterion this quote relates to.'),\n criterion: z.string().describe('Short label describing what this mark scheme criterion evaluates.'),\n met: z.boolean().describe('Whether the quoted text satisfies this criterion.'),\n })).describe('Highlight specific parts of the student response and map each to a mark scheme criterion.'),\n});\n\nfunction buildGradingPrompt(\n question: { question: string; answer: string; type: WorksheetQuestionType; points: number; markScheme: unknown },\n responseText: string,\n): string {\n const markSchemeItems = Array.isArray(question.markScheme) ? question.markScheme as MarkSchemeItem[] : [];\n const markSchemeBlock = markSchemeItems.length > 0\n ? markSchemeItems.map((item, i) =>\n ` ${i + 1}. [id=\"${item.id}\", ${item.points} pts] ${item.description || item.text || `Criterion ${i + 1}`}`\n ).join('\\n')\n : ' No mark scheme provided — grade based on correctness and completeness.';\n\n const maxPoints = question.points || markSchemeItems.reduce((sum, item) => sum + (item.points || 0), 0);\n\n return `Grade the following student response.\n\nQuestion type: ${question.type}\nMaximum points: ${maxPoints}\n\nQUESTION:\n${question.question}\n\nCORRECT ANSWER:\n${question.answer || 'No answer key provided.'}\n\nMARK SCHEME:\n${markSchemeBlock}\n\nSTUDENT RESPONSE:\n${responseText}\n\nINSTRUCTIONS:\n- Evaluate the student response against each mark scheme criterion.\n- Total points must not exceed ${maxPoints}.\n- In markschemeState, use the EXACT \"id\" string from the MARK SCHEME items above (e.g. \"${markSchemeItems[0]?.id || 'ms-xxx'}\"). Do NOT invent new IDs.\n- In annotations, use the same EXACT mark scheme id for \"markSchemeId\".\n- For each criterion, find the exact substring in the student's response that is relevant (the \"quote\" field). Copy the text verbatim — do not paraphrase.\n- If no part of the response addresses a criterion, still include an annotation with an empty quote and met=false.\n- Provide constructive feedback comments.\n- Mark isCorrect=true only if the student earned full or near-full marks.`;\n}\n\nconst gradeWorksheetQuestion = async (worksheetResponseId: string, worksheetQuestionProgressId: string) => {\n\n await removeAllPreviousAIComments(worksheetQuestionProgressId);\n\n const worksheetResponse = await prisma.studentWorksheetResponse.findUnique({\n where: { id: worksheetResponseId },\n include: {\n worksheet: true,\n },\n });\n\n if (!worksheetResponse) {\n logger.error('Worksheet response not found');\n throw new Error('Worksheet response not found');\n } \n\n const studentQuestionProgress = await prisma.studentQuestionProgress.findFirst({\n where: {\n id: worksheetQuestionProgressId,\n },\n include: {\n question: true,\n comments: true,\n },\n });\n\n if (!studentQuestionProgress) {\n logger.error('Student question progress not found', { worksheetQuestionProgressId });\n throw new Error('Student question progress not found');\n }\n\n pusher.trigger(worksheetChannel(worksheetResponse.id), `set-pending`, {\n id: studentQuestionProgress.id,\n });\n\n const question = studentQuestionProgress.question;\n const responseText = studentQuestionProgress.response;\n\n // Short-circuit for empty responses: award 0 points without calling inference\n if (!responseText || responseText.trim().length === 0) {\n const updatedProgress = await prisma.studentQuestionProgress.update({\n where: { id: studentQuestionProgress.id, status: {\n not: { in: [JobStatus.CANCELLED] },\n } },\n data: {\n status: JobStatus.COMPLETED,\n isCorrect: false,\n points: 0,\n markschemeState: {},\n comments: {\n create: [{\n content: 'No answer provided.',\n authorId: getAIUserId(),\n }],\n },\n },\n });\n pusher.trigger(worksheetChannel(worksheetResponse.id), `set-completed`, {\n id: updatedProgress.id,\n });\n return updatedProgress;\n }\n\n try {\n const prompt = buildGradingPrompt(question, responseText);\n\n const apiResponse = await inference(\n prompt,\n gradingResponseSchema,\n {\n reason: 'worksheet_autograde',\n metadata: {\n worksheetResponseId: worksheetResponse.id,\n questionId: worksheetQuestionProgressId,\n studentId: worksheetResponse.studentId,\n },\n },\n ).catch((error) => {\n logger.error('Failed to grade worksheet response', { error });\n throw error;\n });\n\n const maxPoints = question.points || 0;\n const clampedPoints = Math.max(0, Math.min(Math.round((apiResponse as { points: number }).points), maxPoints));\n\n const markSchemeItems = Array.isArray(question.markScheme) ? question.markScheme as MarkSchemeItem[] : [];\n const validMarkSchemeIds = new Set(markSchemeItems.map(item => item.id));\n\n const aiMarkschemeState = (apiResponse as {\n markschemeState: { id: string; correct: boolean }[];\n }).markschemeState;\n\n const allIdsValid = aiMarkschemeState.length > 0 &&\n aiMarkschemeState.every(entry => validMarkSchemeIds.has(entry.id));\n\n let resolvedState: Record<string, boolean | unknown[]> = {};\n\n if (allIdsValid) {\n resolvedState = aiMarkschemeState.reduce((acc, curr) => {\n acc[\"item-\" + curr.id] = curr.correct;\n return acc;\n }, {} as Record<string, boolean>);\n } else if (markSchemeItems.length > 0) {\n logger.warn('AI returned mismatched markscheme IDs, falling back to index mapping', {\n expected: markSchemeItems.map(i => i.id),\n received: aiMarkschemeState.map(i => i.id),\n });\n for (let i = 0; i < markSchemeItems.length; i++) {\n const aiEntry = aiMarkschemeState[i];\n resolvedState[\"item-\" + markSchemeItems[i].id] = aiEntry?.correct ?? false;\n }\n }\n\n const annotations = (apiResponse as {\n annotations: { quote: string; markSchemeId: string; criterion: string; met: boolean }[];\n }).annotations\n .filter(a => a.quote.length === 0 || responseText.includes(a.quote));\n\n // Store annotations separately so the boolean-only markschemeState\n // isn't broken by non-boolean values (the frontend parser rejects\n // the entire object if any value isn't a boolean).\n resolvedState['_annotations'] = annotations;\n\n const updatedStudentQuestionProgress = await prisma.studentQuestionProgress.update({\n where: { id: studentQuestionProgress.id, status: {\n not: {\n in: [JobStatus.CANCELLED],\n },\n } },\n data: {\n status: JobStatus.COMPLETED,\n isCorrect: (apiResponse as { isCorrect: boolean }).isCorrect,\n points: clampedPoints,\n markschemeState: resolvedState,\n comments: {\n create: (apiResponse as {\n comments: string[];\n }).comments.map((commentContent) => ({\n content: commentContent,\n authorId: getAIUserId(),\n })),\n },\n },\n });\n pusher.trigger(worksheetChannel(worksheetResponse.id), `set-completed`, {\n id: updatedStudentQuestionProgress.id,\n });\n\n return updatedStudentQuestionProgress;\n } catch (error) {\n logger.error('Failed to grade worksheet response', { error, worksheetResponseId });\n const { count } = await prisma.studentQuestionProgress.updateMany({\n where: {\n id: studentQuestionProgress.id,\n status: { not: JobStatus.CANCELLED },\n },\n data: { status: JobStatus.FAILED },\n });\n if (count === 0) {\n logger.info('Skipping failed status update for cancelled worksheet question', {\n worksheetResponseId,\n worksheetQuestionProgressId,\n });\n return;\n }\n pusher.trigger(worksheetChannel(worksheetResponse.id), `set-failed`, {\n id: studentQuestionProgress.id,\n });\n throw error;\n }\n}\n\n/**\n * Grades and regrades worksheet (can fixed failed responses)\n * @param worksheetResponseId worksheet response id\n * @returns updated worksheet response\n */\n\nconst DO_NOT_INFERENCE_STATUSES = [JobStatus.CANCELLED, JobStatus.PENDING, JobStatus.COMPLETED];\n\nexport const gradeWorksheetPipeline = async (worksheetResponseId: string) => {\n logger.info('Grading worksheet response', { worksheetResponseId });\n const worksheetResponse = await prisma.studentWorksheetResponse.findUnique({\n where: { id: worksheetResponseId },\n include: {\n worksheet: true,\n responses: {\n where: {\n status: {\n not: {\n in: DO_NOT_INFERENCE_STATUSES,\n },\n },\n question: {\n type: {\n not: {\n in: [WorksheetQuestionType.MULTIPLE_CHOICE, WorksheetQuestionType.TRUE_FALSE],\n }\n },\n },\n },\n include: {\n question: true,\n comments: true,\n },\n },\n },\n });\n\n if (!worksheetResponse) {\n logger.error('Worksheet response not found');\n throw new Error('Worksheet response not found');\n }\n\n // Find worksheet questions that have no StudentQuestionProgress for this response\n // and create empty progress records so they get graded (with 0 points).\n const allQuestions = await prisma.worksheetQuestion.findMany({\n where: {\n worksheetId: worksheetResponse.worksheetId,\n type: {\n notIn: [WorksheetQuestionType.MULTIPLE_CHOICE, WorksheetQuestionType.TRUE_FALSE],\n },\n },\n select: { id: true },\n });\n\n const existingQuestionIds = new Set(\n worksheetResponse.responses.map((r) => r.questionId),\n );\n\n for (const q of allQuestions) {\n if (existingQuestionIds.has(q.id)) continue;\n\n const created = await prisma.studentQuestionProgress.create({\n data: {\n studentId: worksheetResponse.studentId,\n questionId: q.id,\n studentWorksheetResponseId: worksheetResponseId,\n response: '',\n isCorrect: false,\n },\n });\n\n worksheetResponse.responses.push({\n ...created,\n question: { id: q.id } as any,\n comments: [],\n } as any);\n }\n\n for (const response of worksheetResponse.responses) {\n logger.info('Grading question', { questionId: response.questionId });\n\n const { count } = await prisma.studentQuestionProgress.updateMany({\n where: {\n id: response.id,\n status: {\n not: {\n in: DO_NOT_INFERENCE_STATUSES,\n },\n },\n },\n data: { status: JobStatus.PENDING },\n });\n\n if (count === 0) {\n continue;\n }\n\n try {\n await gradeWorksheetQuestion(worksheetResponseId, response.id);\n } catch (questionError) {\n logger.error('Failed to grade individual question, continuing with remaining questions', {\n questionId: response.questionId,\n error: questionError,\n });\n }\n\n };\n};\n\nexport const cancelGradePipeline = async (worksheetResponseId: string, worksheetQuestionProgressId: string) => {\n logger.info('Cancelling auto grading', { worksheetResponseId, worksheetQuestionProgressId });\n\n const worksheetResponse = await prisma.studentWorksheetResponse.findUnique({\n where: { id: worksheetResponseId },\n include: {\n worksheet: true,\n },\n });\n if (!worksheetResponse) {\n logger.error('Worksheet response not found');\n throw new Error('Worksheet response not found');\n }\n const updatedStudentQuestionProgress = await prisma.studentQuestionProgress.update({\n where: { id: worksheetQuestionProgressId },\n data: { status: JobStatus.CANCELLED },\n });\n\n await removeAllPreviousAIComments(worksheetQuestionProgressId);\n\n pusher.trigger(worksheetChannel(worksheetResponse.id), `set-cancelled`, {\n id: updatedStudentQuestionProgress.id,\n });\n\n return updatedStudentQuestionProgress;\n};\n\nexport const regradeAllPipeline = async (worksheetResponseId: string) => {\n logger.info('Regrading all questions for worksheet response', { worksheetResponseId });\n const worksheetResponse = await prisma.studentWorksheetResponse.findUnique({\n where: { id: worksheetResponseId },\n include: {\n worksheet: true,\n responses: {\n where: {\n question: {\n type: {\n not: {\n in: [WorksheetQuestionType.MULTIPLE_CHOICE, WorksheetQuestionType.TRUE_FALSE],\n },\n },\n },\n },\n include: {\n question: true,\n comments: true,\n },\n },\n },\n });\n\n if (!worksheetResponse) {\n logger.error('Worksheet response not found');\n throw new Error('Worksheet response not found');\n }\n\n // Reset all non-cancelled responses to PENDING\n for (const response of worksheetResponse.responses) {\n await prisma.studentQuestionProgress.updateMany({\n where: {\n id: response.id,\n status: { not: JobStatus.CANCELLED },\n },\n data: { status: JobStatus.PENDING },\n });\n\n pusher.trigger(worksheetChannel(worksheetResponse.id), `set-pending`, {\n id: response.id,\n });\n }\n\n // Re-grade each question sequentially\n for (const response of worksheetResponse.responses) {\n try {\n await gradeWorksheetQuestion(worksheetResponseId, response.id);\n } catch (questionError) {\n logger.error('Failed to regrade question, continuing with remaining', {\n questionId: response.questionId,\n error: questionError,\n });\n }\n }\n};\n\nexport const regradeWorksheetPipeline = async (worksheetResponseId: string, worksheetQuestionProgressId: string) => {\n logger.info('Regrading worksheet response', { worksheetResponseId, worksheetQuestionProgressId });\n try {\n const worksheetResponse = await prisma.studentWorksheetResponse.findUnique({\n where: { id: worksheetResponseId, },\n include: {\n worksheet: true,\n },\n });\n \n if (!worksheetResponse) {\n logger.error('Worksheet response not found');\n throw new Error('Worksheet response not found');\n }\n\n const { count } = await prisma.studentQuestionProgress.updateMany({\n where: {\n id: worksheetQuestionProgressId,\n status: { not: JobStatus.CANCELLED },\n },\n data: { status: JobStatus.PENDING },\n });\n\n if (count === 0) {\n logger.info('Skipping regrade for cancelled worksheet question', {\n worksheetResponseId,\n worksheetQuestionProgressId,\n });\n return null;\n }\n\n const updatedStudentQuestionProgress = await prisma.studentQuestionProgress.findUnique({\n where: { id: worksheetQuestionProgressId },\n });\n\n await gradeWorksheetQuestion(worksheetResponseId, worksheetQuestionProgressId);\n\n return updatedStudentQuestionProgress;\n } catch (error) {\n const { count } = await prisma.studentQuestionProgress.updateMany({\n where: {\n id: worksheetQuestionProgressId,\n status: { not: JobStatus.CANCELLED },\n },\n data: { status: JobStatus.FAILED },\n });\n if (count === 0) {\n logger.info('Skipping failed status update for cancelled worksheet question regrade', {\n worksheetResponseId,\n worksheetQuestionProgressId,\n });\n return null;\n }\n const worksheetResponse = await prisma.studentWorksheetResponse.findUnique({\n where: { id: worksheetResponseId, },\n include: {\n worksheet: true,\n },\n });\n if (!worksheetResponse) {\n logger.error('Worksheet response not found');\n throw new Error('Worksheet response not found');\n }\n pusher.trigger(worksheetChannel(worksheetResponse.id), `set-failed`, {\n id: worksheetQuestionProgressId,\n });\n logger.error('Failed to regrade worksheet response', { error, worksheetResponseId, worksheetQuestionProgressId });\n throw error;\n }\n};\n"],"names":[],"mappings":"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","debug_id":"a105dee7-158c-50f0-9aaf-fe4eaa2da55f"}
1
+ {"version":3,"file":"gradeWorksheet.js","sources":["pipelines/gradeWorksheet.ts"],"sourceRoot":"/","sourcesContent":["/**\n * Grade worksheet pipeline – AI-powered grading of worksheet responses.\n * Grades questions via inference API, broadcasts status via Pusher (pending/completed/failed/cancelled).\n */\nimport { JobStatus, WorksheetQuestionType } from \"@prisma/client\";\nimport { prisma } from \"../lib/prisma.js\";\nimport { logger } from \"../utils/logger.js\";\nimport z from \"zod\";\nimport { inference } from \"../utils/inference.js\";\nimport { getAIUserId } from \"../utils/aiUser.js\";\nimport { pusher, worksheetChannel } from \"../lib/pusher.js\";\n\n\nconst removeAllPreviousAIComments = async (worksheetQuestionProgressId: string) => {\n await prisma.comment.deleteMany({\n where: {\n studentQuestionProgressId: worksheetQuestionProgressId,\n authorId: getAIUserId(),\n },\n });\n};\n\ntype MarkSchemeItem = { id: string; points: number; description?: string; text?: string };\n\nconst gradingResponseSchema = z.object({\n isCorrect: z.boolean(),\n points: z.number(),\n markschemeState: z.array(z.object({\n id: z.string(),\n correct: z.boolean(),\n })),\n comments: z.array(z.string()),\n annotations: z.array(z.object({\n quote: z.string().describe('An exact substring from the student response that is relevant to this criterion.'),\n markSchemeId: z.string().describe('The id of the mark scheme criterion this quote relates to.'),\n criterion: z.string().describe('Short label describing what this mark scheme criterion evaluates.'),\n met: z.boolean().describe('Whether the quoted text satisfies this criterion.'),\n })).describe('Highlight specific parts of the student response and map each to a mark scheme criterion.'),\n});\n\nfunction buildGradingPrompt(\n question: { question: string; answer: string; type: WorksheetQuestionType; points: number; markScheme: unknown },\n responseText: string,\n): string {\n const markSchemeItems = Array.isArray(question.markScheme) ? question.markScheme as MarkSchemeItem[] : [];\n const markSchemeBlock = markSchemeItems.length > 0\n ? markSchemeItems.map((item, i) =>\n ` ${i + 1}. [id=\"${item.id}\", ${item.points} pts] ${item.description || item.text || `Criterion ${i + 1}`}`\n ).join('\\n')\n : ' No mark scheme provided — grade based on correctness and completeness.';\n\n const maxPoints = question.points || markSchemeItems.reduce((sum, item) => sum + (item.points || 0), 0);\n\n return `Grade the following student response.\n\nQuestion type: ${question.type}\nMaximum points: ${maxPoints}\n\nQUESTION:\n${question.question}\n\nCORRECT ANSWER:\n${question.answer || 'No answer key provided.'}\n\nMARK SCHEME:\n${markSchemeBlock}\n\nSTUDENT RESPONSE:\n${responseText}\n\nINSTRUCTIONS:\n- Evaluate the student response against each mark scheme criterion.\n- Total points must not exceed ${maxPoints}.\n- In markschemeState, use the EXACT \"id\" string from the MARK SCHEME items above (e.g. \"${markSchemeItems[0]?.id || 'ms-xxx'}\"). Do NOT invent new IDs.\n- In annotations, use the same EXACT mark scheme id for \"markSchemeId\".\n- For each criterion, find the exact substring in the student's response that is relevant (the \"quote\" field). Copy the text verbatim — do not paraphrase.\n- If no part of the response addresses a criterion, still include an annotation with an empty quote and met=false.\n- Provide constructive feedback comments.\n- Mark isCorrect=true only if the student earned full or near-full marks.`;\n}\n\nconst gradeWorksheetQuestion = async (\n worksheetResponseId: string,\n worksheetQuestionProgressId: string,\n actingUserId?: string | null,\n) => {\n\n await removeAllPreviousAIComments(worksheetQuestionProgressId);\n\n const worksheetResponse = await prisma.studentWorksheetResponse.findUnique({\n where: { id: worksheetResponseId },\n include: {\n worksheet: true,\n },\n });\n\n if (!worksheetResponse) {\n logger.error('Worksheet response not found');\n throw new Error('Worksheet response not found');\n } \n\n const studentQuestionProgress = await prisma.studentQuestionProgress.findFirst({\n where: {\n id: worksheetQuestionProgressId,\n },\n include: {\n question: true,\n comments: true,\n },\n });\n\n if (!studentQuestionProgress) {\n logger.error('Student question progress not found', { worksheetQuestionProgressId });\n throw new Error('Student question progress not found');\n }\n\n pusher.trigger(worksheetChannel(worksheetResponse.id), `set-pending`, {\n id: studentQuestionProgress.id,\n });\n\n const question = studentQuestionProgress.question;\n const responseText = studentQuestionProgress.response;\n\n // Short-circuit for empty responses: award 0 points without calling inference\n if (!responseText || responseText.trim().length === 0) {\n const updatedProgress = await prisma.studentQuestionProgress.update({\n where: { id: studentQuestionProgress.id, status: {\n not: { in: [JobStatus.CANCELLED] },\n } },\n data: {\n status: JobStatus.COMPLETED,\n isCorrect: false,\n points: 0,\n markschemeState: {},\n comments: {\n create: [{\n content: 'No answer provided.',\n authorId: getAIUserId(),\n }],\n },\n },\n });\n pusher.trigger(worksheetChannel(worksheetResponse.id), `set-completed`, {\n id: updatedProgress.id,\n });\n return updatedProgress;\n }\n\n try {\n const prompt = buildGradingPrompt(question, responseText);\n\n const apiResponse = await inference(\n prompt,\n gradingResponseSchema,\n {\n reason: 'worksheet_autograde',\n metadata: {\n worksheetResponseId: worksheetResponse.id,\n questionId: worksheetQuestionProgressId,\n studentId: worksheetResponse.studentId,\n },\n attribution: {\n userId: actingUserId ?? worksheetResponse.studentId,\n classId: worksheetResponse.worksheet.classId,\n },\n },\n ).catch((error) => {\n logger.error('Failed to grade worksheet response', { error });\n throw error;\n });\n\n const maxPoints = question.points || 0;\n const clampedPoints = Math.max(0, Math.min(Math.round((apiResponse as { points: number }).points), maxPoints));\n\n const markSchemeItems = Array.isArray(question.markScheme) ? question.markScheme as MarkSchemeItem[] : [];\n const validMarkSchemeIds = new Set(markSchemeItems.map(item => item.id));\n\n const aiMarkschemeState = (apiResponse as {\n markschemeState: { id: string; correct: boolean }[];\n }).markschemeState;\n\n const allIdsValid = aiMarkschemeState.length > 0 &&\n aiMarkschemeState.every(entry => validMarkSchemeIds.has(entry.id));\n\n let resolvedState: Record<string, boolean | unknown[]> = {};\n\n if (allIdsValid) {\n resolvedState = aiMarkschemeState.reduce((acc, curr) => {\n acc[\"item-\" + curr.id] = curr.correct;\n return acc;\n }, {} as Record<string, boolean>);\n } else if (markSchemeItems.length > 0) {\n logger.warn('AI returned mismatched markscheme IDs, falling back to index mapping', {\n expected: markSchemeItems.map(i => i.id),\n received: aiMarkschemeState.map(i => i.id),\n });\n for (let i = 0; i < markSchemeItems.length; i++) {\n const aiEntry = aiMarkschemeState[i];\n resolvedState[\"item-\" + markSchemeItems[i].id] = aiEntry?.correct ?? false;\n }\n }\n\n const annotations = (apiResponse as {\n annotations: { quote: string; markSchemeId: string; criterion: string; met: boolean }[];\n }).annotations\n .filter(a => a.quote.length === 0 || responseText.includes(a.quote));\n\n // Store annotations separately so the boolean-only markschemeState\n // isn't broken by non-boolean values (the frontend parser rejects\n // the entire object if any value isn't a boolean).\n resolvedState['_annotations'] = annotations;\n\n const updatedStudentQuestionProgress = await prisma.studentQuestionProgress.update({\n where: { id: studentQuestionProgress.id, status: {\n not: {\n in: [JobStatus.CANCELLED],\n },\n } },\n data: {\n status: JobStatus.COMPLETED,\n isCorrect: (apiResponse as { isCorrect: boolean }).isCorrect,\n points: clampedPoints,\n markschemeState: resolvedState,\n comments: {\n create: (apiResponse as {\n comments: string[];\n }).comments.map((commentContent) => ({\n content: commentContent,\n authorId: getAIUserId(),\n })),\n },\n },\n });\n pusher.trigger(worksheetChannel(worksheetResponse.id), `set-completed`, {\n id: updatedStudentQuestionProgress.id,\n });\n\n return updatedStudentQuestionProgress;\n } catch (error) {\n logger.error('Failed to grade worksheet response', { error, worksheetResponseId });\n const { count } = await prisma.studentQuestionProgress.updateMany({\n where: {\n id: studentQuestionProgress.id,\n status: { not: JobStatus.CANCELLED },\n },\n data: { status: JobStatus.FAILED },\n });\n if (count === 0) {\n logger.info('Skipping failed status update for cancelled worksheet question', {\n worksheetResponseId,\n worksheetQuestionProgressId,\n });\n return;\n }\n pusher.trigger(worksheetChannel(worksheetResponse.id), `set-failed`, {\n id: studentQuestionProgress.id,\n });\n throw error;\n }\n}\n\n/**\n * Grades and regrades worksheet (can fixed failed responses)\n * @param worksheetResponseId worksheet response id\n * @returns updated worksheet response\n */\n\nconst DO_NOT_INFERENCE_STATUSES = [JobStatus.CANCELLED, JobStatus.PENDING, JobStatus.COMPLETED];\n\nexport const gradeWorksheetPipeline = async (\n worksheetResponseId: string,\n actingUserId?: string | null,\n) => {\n logger.info('Grading worksheet response', { worksheetResponseId });\n const worksheetResponse = await prisma.studentWorksheetResponse.findUnique({\n where: { id: worksheetResponseId },\n include: {\n worksheet: true,\n responses: {\n where: {\n status: {\n not: {\n in: DO_NOT_INFERENCE_STATUSES,\n },\n },\n question: {\n type: {\n not: {\n in: [WorksheetQuestionType.MULTIPLE_CHOICE, WorksheetQuestionType.TRUE_FALSE],\n }\n },\n },\n },\n include: {\n question: true,\n comments: true,\n },\n },\n },\n });\n\n if (!worksheetResponse) {\n logger.error('Worksheet response not found');\n throw new Error('Worksheet response not found');\n }\n\n // Find worksheet questions that have no StudentQuestionProgress for this response\n // and create empty progress records so they get graded (with 0 points).\n const allQuestions = await prisma.worksheetQuestion.findMany({\n where: {\n worksheetId: worksheetResponse.worksheetId,\n type: {\n notIn: [WorksheetQuestionType.MULTIPLE_CHOICE, WorksheetQuestionType.TRUE_FALSE],\n },\n },\n select: { id: true },\n });\n\n const existingQuestionIds = new Set(\n worksheetResponse.responses.map((r) => r.questionId),\n );\n\n for (const q of allQuestions) {\n if (existingQuestionIds.has(q.id)) continue;\n\n const created = await prisma.studentQuestionProgress.create({\n data: {\n studentId: worksheetResponse.studentId,\n questionId: q.id,\n studentWorksheetResponseId: worksheetResponseId,\n response: '',\n isCorrect: false,\n },\n });\n\n worksheetResponse.responses.push({\n ...created,\n question: { id: q.id } as any,\n comments: [],\n } as any);\n }\n\n for (const response of worksheetResponse.responses) {\n logger.info('Grading question', { questionId: response.questionId });\n\n const { count } = await prisma.studentQuestionProgress.updateMany({\n where: {\n id: response.id,\n status: {\n not: {\n in: DO_NOT_INFERENCE_STATUSES,\n },\n },\n },\n data: { status: JobStatus.PENDING },\n });\n\n if (count === 0) {\n continue;\n }\n\n try {\n await gradeWorksheetQuestion(worksheetResponseId, response.id, actingUserId);\n } catch (questionError) {\n logger.error('Failed to grade individual question, continuing with remaining questions', {\n questionId: response.questionId,\n error: questionError,\n });\n }\n\n };\n};\n\nexport const cancelGradePipeline = async (worksheetResponseId: string, worksheetQuestionProgressId: string) => {\n logger.info('Cancelling auto grading', { worksheetResponseId, worksheetQuestionProgressId });\n\n const worksheetResponse = await prisma.studentWorksheetResponse.findUnique({\n where: { id: worksheetResponseId },\n include: {\n worksheet: true,\n },\n });\n if (!worksheetResponse) {\n logger.error('Worksheet response not found');\n throw new Error('Worksheet response not found');\n }\n const updatedStudentQuestionProgress = await prisma.studentQuestionProgress.update({\n where: { id: worksheetQuestionProgressId },\n data: { status: JobStatus.CANCELLED },\n });\n\n await removeAllPreviousAIComments(worksheetQuestionProgressId);\n\n pusher.trigger(worksheetChannel(worksheetResponse.id), `set-cancelled`, {\n id: updatedStudentQuestionProgress.id,\n });\n\n return updatedStudentQuestionProgress;\n};\n\nexport const regradeAllPipeline = async (\n worksheetResponseId: string,\n actingUserId?: string | null,\n) => {\n logger.info('Regrading all questions for worksheet response', { worksheetResponseId });\n const worksheetResponse = await prisma.studentWorksheetResponse.findUnique({\n where: { id: worksheetResponseId },\n include: {\n worksheet: true,\n responses: {\n where: {\n question: {\n type: {\n not: {\n in: [WorksheetQuestionType.MULTIPLE_CHOICE, WorksheetQuestionType.TRUE_FALSE],\n },\n },\n },\n },\n include: {\n question: true,\n comments: true,\n },\n },\n },\n });\n\n if (!worksheetResponse) {\n logger.error('Worksheet response not found');\n throw new Error('Worksheet response not found');\n }\n\n // Reset all non-cancelled responses to PENDING\n for (const response of worksheetResponse.responses) {\n await prisma.studentQuestionProgress.updateMany({\n where: {\n id: response.id,\n status: { not: JobStatus.CANCELLED },\n },\n data: { status: JobStatus.PENDING },\n });\n\n pusher.trigger(worksheetChannel(worksheetResponse.id), `set-pending`, {\n id: response.id,\n });\n }\n\n // Re-grade each question sequentially\n for (const response of worksheetResponse.responses) {\n try {\n await gradeWorksheetQuestion(worksheetResponseId, response.id, actingUserId);\n } catch (questionError) {\n logger.error('Failed to regrade question, continuing with remaining', {\n questionId: response.questionId,\n error: questionError,\n });\n }\n }\n};\n\nexport const regradeWorksheetPipeline = async (\n worksheetResponseId: string,\n worksheetQuestionProgressId: string,\n actingUserId?: string | null,\n) => {\n logger.info('Regrading worksheet response', { worksheetResponseId, worksheetQuestionProgressId });\n try {\n const worksheetResponse = await prisma.studentWorksheetResponse.findUnique({\n where: { id: worksheetResponseId, },\n include: {\n worksheet: true,\n },\n });\n \n if (!worksheetResponse) {\n logger.error('Worksheet response not found');\n throw new Error('Worksheet response not found');\n }\n\n const { count } = await prisma.studentQuestionProgress.updateMany({\n where: {\n id: worksheetQuestionProgressId,\n status: { not: JobStatus.CANCELLED },\n },\n data: { status: JobStatus.PENDING },\n });\n\n if (count === 0) {\n logger.info('Skipping regrade for cancelled worksheet question', {\n worksheetResponseId,\n worksheetQuestionProgressId,\n });\n return null;\n }\n\n const updatedStudentQuestionProgress = await prisma.studentQuestionProgress.findUnique({\n where: { id: worksheetQuestionProgressId },\n });\n\n await gradeWorksheetQuestion(worksheetResponseId, worksheetQuestionProgressId, actingUserId);\n\n return updatedStudentQuestionProgress;\n } catch (error) {\n const { count } = await prisma.studentQuestionProgress.updateMany({\n where: {\n id: worksheetQuestionProgressId,\n status: { not: JobStatus.CANCELLED },\n },\n data: { status: JobStatus.FAILED },\n });\n if (count === 0) {\n logger.info('Skipping failed status update for cancelled worksheet question regrade', {\n worksheetResponseId,\n worksheetQuestionProgressId,\n });\n return null;\n }\n const worksheetResponse = await prisma.studentWorksheetResponse.findUnique({\n where: { id: worksheetResponseId, },\n include: {\n worksheet: true,\n },\n });\n if (!worksheetResponse) {\n logger.error('Worksheet response not found');\n throw new Error('Worksheet response not found');\n }\n pusher.trigger(worksheetChannel(worksheetResponse.id), `set-failed`, {\n id: worksheetQuestionProgressId,\n });\n logger.error('Failed to regrade worksheet response', { error, worksheetResponseId, worksheetQuestionProgressId });\n throw error;\n }\n};\n"],"names":[],"mappings":"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","debug_id":"60823007-f8d4-5536-b222-c825a7db8909"}
@@ -21,6 +21,8 @@ export interface PdfToWorksheetOptions {
21
21
  * channel. This is the path used by the BullMQ worker.
22
22
  */
23
23
  worksheetId?: string;
24
+ /** Acting user for AI usage attribution. */
25
+ userId?: string | null;
24
26
  }
25
27
  /**
26
28
  * Sends a PDF URL to the model for remote retrieval, extracts a worksheet via
@@ -1 +1 @@
1
- {"version":3,"file":"pdfToWorksheet.d.ts","sourceRoot":"/","sources":["pipelines/pdfToWorksheet.ts"],"names":[],"mappings":"AAiBA,OAAO,EAAE,SAAS,EAAU,MAAM,gBAAgB,CAAC;AAmDnD;;;;;;;GAOG;AACH,MAAM,WAAW,uBAAuB;IACtC,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,SAAS,CAAC;IAClB,yEAAyE;IACzE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AA4FD,MAAM,WAAW,qBAAqB;IACpC;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;GAQG;AACH,wBAAsB,cAAc,CAClC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,qBAA0B;;;;;;;;;UA4LpC"}
1
+ {"version":3,"file":"pdfToWorksheet.d.ts","sourceRoot":"/","sources":["pipelines/pdfToWorksheet.ts"],"names":[],"mappings":"AAiBA,OAAO,EAAE,SAAS,EAAU,MAAM,gBAAgB,CAAC;AAmDnD;;;;;;;GAOG;AACH,MAAM,WAAW,uBAAuB;IACtC,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,SAAS,CAAC;IAClB,yEAAyE;IACzE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AA4FD,MAAM,WAAW,qBAAqB;IACpC;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,4CAA4C;IAC5C,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AAED;;;;;;;;GAQG;AACH,wBAAsB,cAAc,CAClC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,qBAA0B;;;;;;;;;UA6LpC"}
@@ -14,7 +14,7 @@
14
14
  * supplied the pipeline creates the row itself.
15
15
  */
16
16
 
17
- !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]="498d4f3f-e9a4-58fb-8e7a-ed49c5f116e7")}catch(e){}}();
17
+ !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]="d76f2a44-92c7-5185-9060-1b4bf66db0e1")}catch(e){}}();
18
18
  import { z } from "zod";
19
19
  import { JobStatus, Prisma } from "@prisma/client";
20
20
  import { prisma } from "../lib/prisma.js";
@@ -199,6 +199,7 @@ export async function pdfToWorksheet(gcsUrl, classId, options = {}) {
199
199
  classId,
200
200
  filename,
201
201
  },
202
+ attribution: { userId: options.userId, classId },
202
203
  });
203
204
  }
204
205
  catch (error) {
@@ -325,4 +326,4 @@ async function markWorksheetFailed(worksheetId, errorMessage) {
325
326
  await emitStatus(worksheetId, JobStatus.FAILED);
326
327
  }
327
328
  //# sourceMappingURL=pdfToWorksheet.js.map
328
- //# debugId=498d4f3f-e9a4-58fb-8e7a-ed49c5f116e7
329
+ //# debugId=d76f2a44-92c7-5185-9060-1b4bf66db0e1
@@ -1 +1 @@
1
- {"version":3,"file":"pdfToWorksheet.js","sources":["pipelines/pdfToWorksheet.ts"],"sourceRoot":"/","sourcesContent":["/**\n * PDF URL → worksheet: send a remote PDF URL to structured inference,\n * then persist a Worksheet and WorksheetQuestion rows.\n *\n * Normally driven by a BullMQ worker, so the producer (e.g.\n * `createWorksheetFromUploadedPdf`) creates a placeholder Worksheet row up\n * front and passes its id in via `worksheetId`. The pipeline broadcasts\n * `Worksheet.status` transitions on the per-worksheet job Pusher channel\n * (`set-pending` / `set-completed` / `set-failed` / `set-cancelled`) — same\n * convention as `gradeWorksheet`.\n *\n * The legacy `pdfToWorksheet(gcsUrl, classId)` signature is preserved for\n * direct/synchronous callers (such as tests): when no `worksheetId` is\n * supplied the pipeline creates the row itself.\n */\nimport { z } from \"zod\";\nimport type { ResponseInput } from \"openai/resources/responses/responses.js\";\nimport { JobStatus, Prisma } from \"@prisma/client\";\nimport { prisma } from \"../lib/prisma.js\";\nimport { inference } from \"../utils/inference.js\";\nimport { logger } from \"../utils/logger.js\";\nimport { pusher, worksheetJobChannel } from \"../lib/pusher.js\";\n\n/** Restrict remote fetches to Google-hosted object URLs. */\nfunction isProbablyGoogleCloudUrl(url: string): boolean {\n try {\n const host = new URL(url).hostname;\n return (\n host.endsWith(\"storage.googleapis.com\") ||\n host.endsWith(\"googleusercontent.com\") ||\n host.includes(\"storage.cloud.google.com\")\n );\n } catch {\n return false;\n }\n}\n\nfunction getFilenameFromUrl(url: string): string {\n try {\n const parsed = new URL(url);\n const lastSegment = parsed.pathname.split(\"/\").filter(Boolean).pop();\n return lastSegment && lastSegment.includes(\".\") ? lastSegment : \"document.pdf\";\n } catch {\n return \"document.pdf\";\n }\n}\n\nfunction sanitizeUrlForLogs(url: string): string {\n try {\n const parsed = new URL(url);\n return `${parsed.origin}${parsed.pathname}`;\n } catch {\n return 'invalid-url';\n }\n}\n\n/**\n * Pusher event names mirror `gradeWorksheet`'s convention so clients can\n * share the same JobStatus → event-name mapping.\n */\nconst STATUS_EVENT_NAMES: Record<JobStatus, string> = {\n NOT_STARTED: 'set-not-started',\n PENDING: 'set-pending',\n COMPLETED: 'set-completed',\n FAILED: 'set-failed',\n CANCELLED: 'set-cancelled',\n};\n\n/**\n * Realtime payload for the per-worksheet job channel.\n *\n * On COMPLETED we ship the new title and question count so clients can\n * patch their card optimistically and side-step replica lag on the next\n * refetch. Error strings are intentionally NOT broadcast — they may carry\n * server internals; the full error stays on `RunJob.error` for ops only.\n */\nexport interface WorksheetJobStatusEvent {\n worksheetId: string;\n status: JobStatus;\n /** Filled on COMPLETED so clients can avoid a replica-lagged refetch. */\n name?: string;\n questionCount?: number;\n}\n\n/**\n * Fire-and-forget broadcast of a `Worksheet.status` transition. We never let\n * a Pusher outage break the actual pipeline: the persisted `Worksheet.status`\n * is the source of truth, the channel just nudges clients.\n */\nasync function emitStatus(\n worksheetId: string | undefined,\n status: JobStatus,\n extras: Pick<WorksheetJobStatusEvent, 'name' | 'questionCount'> = {},\n): Promise<void> {\n if (!worksheetId) return;\n\n try {\n await pusher.trigger(\n worksheetJobChannel(worksheetId),\n STATUS_EVENT_NAMES[status],\n { worksheetId, status, ...extras } satisfies WorksheetJobStatusEvent,\n );\n } catch (error) {\n logger.warn('pdfToWorksheet: failed to broadcast status update', {\n worksheetId,\n status,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n}\n\n/** LLM output shape (validated by inference). */\nconst worksheetDraftSchema = z.object({\n title: z.string(),\n questions: z\n .array(\n z.object({\n type: z.enum([\n \"MULTIPLE_CHOICE\",\n \"TRUE_FALSE\",\n \"SHORT_ANSWER\",\n \"LONG_ANSWER\",\n \"MATH_EXPRESSION\",\n \"ESSAY\",\n ]),\n question: z.string(),\n answer: z.string(),\n options: z\n .array(\n z.object({\n id: z.string(),\n text: z.string(),\n isCorrect: z.boolean(),\n })\n )\n .optional()\n .default([]),\n markScheme: z\n .array(\n z.object({\n id: z.string(),\n points: z.number().transform((value) => Math.round(value)),\n description: z.string(),\n })\n )\n .optional()\n .default([]),\n points: z.number().optional().default(0),\n order: z.number().transform((value) => Math.round(value)),\n })\n )\n .default([]),\n});\n\ntype WorksheetDraft = z.infer<typeof worksheetDraftSchema>;\n\nconst schemaInstructions = `Return ONLY valid JSON matching this schema:\n{\n \"title\": string,\n \"questions\": [\n {\n \"type\": \"MULTIPLE_CHOICE\" | \"TRUE_FALSE\" | \"SHORT_ANSWER\" | \"LONG_ANSWER\" | \"MATH_EXPRESSION\" | \"ESSAY\",\n \"question\": string,\n \"answer\": string,\n \"options\": [ { \"id\": string, \"text\": string, \"isCorrect\": boolean } ],\n \"markScheme\": [ { \"id\": string, \"points\": number, \"description\": string } ],\n \"points\": number,\n \"order\": number\n }\n ]\n}\nRules: \"questions\" is an array ([] if none). Non-MCQ \"options\" can be []. Use \"order\" 1,2,3,… in reading order. No markdown or extra keys.\nIf no answer is provided for a multiple-choice or true/false question, determine the answer from the options.`;\n\nexport interface PdfToWorksheetOptions {\n /**\n * Pre-existing Worksheet row id. When supplied, the pipeline fills in this\n * row instead of creating a new one and broadcasts progress on its job\n * channel. This is the path used by the BullMQ worker.\n */\n worksheetId?: string;\n}\n\n/**\n * Sends a PDF URL to the model for remote retrieval, extracts a worksheet via\n * structured inference, and inserts (or back-fills) worksheet + questions.\n *\n * When called with `options.worksheetId` the pipeline:\n * - keeps that row's id stable (the client already routed to it),\n * - drives `status` PENDING → COMPLETED / FAILED,\n * - fans out `set-*` Pusher events on `worksheetJobChannel`.\n */\nexport async function pdfToWorksheet(\n gcsUrl: string,\n classId: string,\n options: PdfToWorksheetOptions = {},\n) {\n const sourceUrl = sanitizeUrlForLogs(gcsUrl);\n const placeholderWorksheetId = options.worksheetId;\n\n await emitStatus(placeholderWorksheetId, JobStatus.PENDING);\n\n if (!isProbablyGoogleCloudUrl(gcsUrl)) {\n logger.warn(\"pdfToWorksheet: rejected unsupported PDF URL\", {\n sanitizedUrl: sourceUrl,\n });\n await markWorksheetFailed(\n placeholderWorksheetId,\n \"Only Google Cloud Storage URLs are supported\",\n );\n throw new Error(\"Only Google Cloud Storage URLs are supported\");\n }\n\n // Look for a previously-completed worksheet pointing at the same source PDF.\n // If there is one (and it's not the placeholder we were handed) we reuse it,\n // mark the placeholder CANCELLED so the editor stops spinning, and return\n // the canonical worksheet.\n const existingForSource = await prisma.worksheet.findUnique({\n where: { classId_sourceUrl: { classId, sourceUrl } },\n });\n\n if (existingForSource && existingForSource.id !== placeholderWorksheetId) {\n logger.info(\"pdfToWorksheet: reusing existing worksheet for source PDF\", {\n worksheetId: existingForSource.id,\n classId,\n sourceUrl,\n });\n\n if (placeholderWorksheetId) {\n await prisma.worksheet\n .update({\n where: { id: placeholderWorksheetId },\n data: { status: JobStatus.CANCELLED },\n })\n .catch(() => undefined);\n\n await emitStatus(placeholderWorksheetId, JobStatus.CANCELLED);\n }\n\n return existingForSource;\n }\n\n const filename = getFilenameFromUrl(gcsUrl);\n const prompt = `Read the attached PDF and convert it into a single worksheet JSON object.\\n\\n${schemaInstructions}`;\n\n // Force Responses API input types so we can pass a remote PDF via `file_url`.\n const input: ResponseInput = [\n {\n role: \"user\",\n content: [\n { type: \"input_text\", text: prompt },\n { type: \"input_file\", file_url: gcsUrl },\n ],\n },\n ];\n\n let draft: WorksheetDraft | null;\n try {\n draft = await inference<WorksheetDraft>(\n input,\n worksheetDraftSchema,\n {\n reason: 'pdf_to_worksheet_draft',\n metadata: {\n classId,\n filename,\n },\n }\n );\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n await markWorksheetFailed(placeholderWorksheetId, message);\n throw error;\n }\n\n if (!draft) {\n logger.warn(\"pdfToWorksheet: no model output\", { filename });\n await markWorksheetFailed(\n placeholderWorksheetId,\n \"Model returned no parseable worksheet output\",\n );\n return null;\n }\n\n try {\n const worksheet = await prisma.$transaction(async (tx) => {\n const created = placeholderWorksheetId\n ? await tx.worksheet.update({\n where: { id: placeholderWorksheetId },\n data: {\n classId,\n name: draft!.title,\n sourceUrl,\n status: JobStatus.PENDING,\n },\n })\n : await tx.worksheet.create({\n data: {\n classId,\n name: draft!.title,\n sourceUrl,\n status: JobStatus.PENDING,\n },\n });\n\n if (draft!.questions.length > 0) {\n // When re-running against an existing placeholder, drop any prior\n // questions so we don't accumulate duplicates from retries.\n if (placeholderWorksheetId) {\n await tx.worksheetQuestion.deleteMany({\n where: { worksheetId: created.id },\n });\n }\n\n await tx.worksheetQuestion.createMany({\n data: draft!.questions.map((question) => ({\n worksheetId: created.id,\n type: question.type,\n question: question.question,\n answer: question.answer,\n points: Math.round(question.points ?? 0),\n options: question.options ?? [],\n markScheme: (question.markScheme ?? []).map((item) => ({\n ...item,\n points: Math.round(item.points),\n })),\n order: Math.round(question.order),\n })),\n });\n }\n\n return tx.worksheet.update({\n where: { id: created.id },\n data: { status: JobStatus.COMPLETED },\n });\n });\n\n await emitStatus(placeholderWorksheetId, JobStatus.COMPLETED, {\n name: worksheet.name,\n questionCount: draft.questions.length,\n });\n\n return worksheet;\n } catch (error) {\n if (\n error instanceof Prisma.PrismaClientKnownRequestError &&\n error.code === \"P2002\"\n ) {\n const worksheet = await prisma.worksheet.findUnique({\n where: {\n classId_sourceUrl: {\n classId,\n sourceUrl,\n },\n },\n });\n\n if (worksheet) {\n logger.info(\"pdfToWorksheet: reused worksheet created by concurrent retry\", {\n worksheetId: worksheet.id,\n classId,\n sourceUrl,\n });\n\n if (placeholderWorksheetId && placeholderWorksheetId !== worksheet.id) {\n await prisma.worksheet\n .update({\n where: { id: placeholderWorksheetId },\n data: { status: JobStatus.CANCELLED },\n })\n .catch(() => undefined);\n\n await emitStatus(placeholderWorksheetId, JobStatus.CANCELLED);\n }\n\n return worksheet;\n }\n }\n\n const message = error instanceof Error ? error.message : String(error);\n await markWorksheetFailed(placeholderWorksheetId, message);\n throw error;\n }\n}\n\nasync function markWorksheetFailed(\n worksheetId: string | undefined,\n errorMessage: string,\n) {\n if (!worksheetId) return;\n\n await prisma.worksheet\n .update({\n where: { id: worksheetId },\n data: { status: JobStatus.FAILED },\n })\n .catch((updateError) => {\n logger.error('pdfToWorksheet: failed to mark worksheet FAILED', {\n worksheetId,\n error:\n updateError instanceof Error\n ? updateError.message\n : String(updateError),\n });\n });\n\n // The full error is logged + persisted on `RunJob.error`; the broadcast\n // intentionally carries no error payload to avoid leaking internals.\n logger.error('pdfToWorksheet: marking worksheet FAILED', {\n worksheetId,\n error: errorMessage,\n });\n\n await emitStatus(worksheetId, JobStatus.FAILED);\n}\n"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;;;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC1C,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAClD,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAC5C,OAAO,EAAE,MAAM,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAE/D,4DAA4D;AAC5D,SAAS,wBAAwB,CAAC,GAAW;IAC3C,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC;QACnC,OAAO,CACL,IAAI,CAAC,QAAQ,CAAC,wBAAwB,CAAC;YACvC,IAAI,CAAC,QAAQ,CAAC,uBAAuB,CAAC;YACtC,IAAI,CAAC,QAAQ,CAAC,0BAA0B,CAAC,CAC1C,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,SAAS,kBAAkB,CAAC,GAAW;IACrC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;QAC5B,MAAM,WAAW,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,CAAC;QACrE,OAAO,WAAW,IAAI,WAAW,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,cAAc,CAAC;IACjF,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,cAAc,CAAC;IACxB,CAAC;AACH,CAAC;AAED,SAAS,kBAAkB,CAAC,GAAW;IACrC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;QAC5B,OAAO,GAAG,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC,QAAQ,EAAE,CAAC;IAC9C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,aAAa,CAAC;IACvB,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,kBAAkB,GAA8B;IACpD,WAAW,EAAE,iBAAiB;IAC9B,OAAO,EAAE,aAAa;IACtB,SAAS,EAAE,eAAe;IAC1B,MAAM,EAAE,YAAY;IACpB,SAAS,EAAE,eAAe;CAC3B,CAAC;AAkBF;;;;GAIG;AACH,KAAK,UAAU,UAAU,CACvB,WAA+B,EAC/B,MAAiB,EACjB,SAAkE,EAAE;IAEpE,IAAI,CAAC,WAAW;QAAE,OAAO;IAEzB,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,OAAO,CAClB,mBAAmB,CAAC,WAAW,CAAC,EAChC,kBAAkB,CAAC,MAAM,CAAC,EAC1B,EAAE,WAAW,EAAE,MAAM,EAAE,GAAG,MAAM,EAAoC,CACrE,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,CAAC,IAAI,CAAC,mDAAmD,EAAE;YAC/D,WAAW;YACX,MAAM;YACN,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;SAC9D,CAAC,CAAC;IACL,CAAC;AACH,CAAC;AAED,iDAAiD;AACjD,MAAM,oBAAoB,GAAG,CAAC,CAAC,MAAM,CAAC;IACpC,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;IACjB,SAAS,EAAE,CAAC;SACT,KAAK,CACJ,CAAC,CAAC,MAAM,CAAC;QACP,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC;YACX,iBAAiB;YACjB,YAAY;YACZ,cAAc;YACd,aAAa;YACb,iBAAiB;YACjB,OAAO;SACR,CAAC;QACF,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE;QACpB,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE;QAClB,OAAO,EAAE,CAAC;aACP,KAAK,CACJ,CAAC,CAAC,MAAM,CAAC;YACP,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE;YACd,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;YAChB,SAAS,EAAE,CAAC,CAAC,OAAO,EAAE;SACvB,CAAC,CACH;aACA,QAAQ,EAAE;aACV,OAAO,CAAC,EAAE,CAAC;QACd,UAAU,EAAE,CAAC;aACV,KAAK,CACF,CAAC,CAAC,MAAM,CAAC;YACP,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE;YACd,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,SAAS,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;YAC1D,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE;SACxB,CAAC,CACL;aACA,QAAQ,EAAE;aACV,OAAO,CAAC,EAAE,CAAC;QACd,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;QACxC,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,SAAS,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;KAC1D,CAAC,CACH;SACA,OAAO,CAAC,EAAE,CAAC;CACf,CAAC,CAAC;AAIH,MAAM,kBAAkB,GAAG;;;;;;;;;;;;;;;;8GAgBmF,CAAC;AAW/G;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,MAAc,EACd,OAAe,EACf,UAAiC,EAAE;IAEnC,MAAM,SAAS,GAAG,kBAAkB,CAAC,MAAM,CAAC,CAAC;IAC7C,MAAM,sBAAsB,GAAG,OAAO,CAAC,WAAW,CAAC;IAEnD,MAAM,UAAU,CAAC,sBAAsB,EAAE,SAAS,CAAC,OAAO,CAAC,CAAC;IAE5D,IAAI,CAAC,wBAAwB,CAAC,MAAM,CAAC,EAAE,CAAC;QACtC,MAAM,CAAC,IAAI,CAAC,8CAA8C,EAAE;YAC1D,YAAY,EAAE,SAAS;SACxB,CAAC,CAAC;QACH,MAAM,mBAAmB,CACvB,sBAAsB,EACtB,8CAA8C,CAC/C,CAAC;QACF,MAAM,IAAI,KAAK,CAAC,8CAA8C,CAAC,CAAC;IAClE,CAAC;IAED,6EAA6E;IAC7E,6EAA6E;IAC7E,0EAA0E;IAC1E,2BAA2B;IAC3B,MAAM,iBAAiB,GAAG,MAAM,MAAM,CAAC,SAAS,CAAC,UAAU,CAAC;QAC1D,KAAK,EAAE,EAAE,iBAAiB,EAAE,EAAE,OAAO,EAAE,SAAS,EAAE,EAAE;KACrD,CAAC,CAAC;IAEH,IAAI,iBAAiB,IAAI,iBAAiB,CAAC,EAAE,KAAK,sBAAsB,EAAE,CAAC;QACzE,MAAM,CAAC,IAAI,CAAC,2DAA2D,EAAE;YACvE,WAAW,EAAE,iBAAiB,CAAC,EAAE;YACjC,OAAO;YACP,SAAS;SACV,CAAC,CAAC;QAEH,IAAI,sBAAsB,EAAE,CAAC;YAC3B,MAAM,MAAM,CAAC,SAAS;iBACnB,MAAM,CAAC;gBACN,KAAK,EAAE,EAAE,EAAE,EAAE,sBAAsB,EAAE;gBACrC,IAAI,EAAE,EAAE,MAAM,EAAE,SAAS,CAAC,SAAS,EAAE;aACtC,CAAC;iBACD,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;YAE1B,MAAM,UAAU,CAAC,sBAAsB,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;QAChE,CAAC;QAED,OAAO,iBAAiB,CAAC;IAC3B,CAAC;IAED,MAAM,QAAQ,GAAG,kBAAkB,CAAC,MAAM,CAAC,CAAC;IAC5C,MAAM,MAAM,GAAG,gFAAgF,kBAAkB,EAAE,CAAC;IAEpH,8EAA8E;IAC9E,MAAM,KAAK,GAAkB;QAC3B;YACE,IAAI,EAAE,MAAM;YACZ,OAAO,EAAE;gBACP,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,EAAE;gBACpC,EAAE,IAAI,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,EAAE;aACzC;SACF;KACF,CAAC;IAEF,IAAI,KAA4B,CAAC;IACjC,IAAI,CAAC;QACH,KAAK,GAAG,MAAM,SAAS,CACrB,KAAK,EACL,oBAAoB,EACpB;YACE,MAAM,EAAE,wBAAwB;YAChC,QAAQ,EAAE;gBACR,OAAO;gBACP,QAAQ;aACT;SACF,CACF,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACvE,MAAM,mBAAmB,CAAC,sBAAsB,EAAE,OAAO,CAAC,CAAC;QAC3D,MAAM,KAAK,CAAC;IACd,CAAC;IAED,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,CAAC,IAAI,CAAC,iCAAiC,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAC;QAC7D,MAAM,mBAAmB,CACvB,sBAAsB,EACtB,8CAA8C,CAC/C,CAAC;QACF,OAAO,IAAI,CAAC;IACd,CAAC;IAED,IAAI,CAAC;QACH,MAAM,SAAS,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC,KAAK,EAAE,EAAE,EAAE,EAAE;YACvD,MAAM,OAAO,GAAG,sBAAsB;gBACpC,CAAC,CAAC,MAAM,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC;oBACxB,KAAK,EAAE,EAAE,EAAE,EAAE,sBAAsB,EAAE;oBACrC,IAAI,EAAE;wBACJ,OAAO;wBACP,IAAI,EAAE,KAAM,CAAC,KAAK;wBAClB,SAAS;wBACT,MAAM,EAAE,SAAS,CAAC,OAAO;qBAC1B;iBACF,CAAC;gBACJ,CAAC,CAAC,MAAM,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC;oBACxB,IAAI,EAAE;wBACJ,OAAO;wBACP,IAAI,EAAE,KAAM,CAAC,KAAK;wBAClB,SAAS;wBACT,MAAM,EAAE,SAAS,CAAC,OAAO;qBAC1B;iBACF,CAAC,CAAC;YAEP,IAAI,KAAM,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAChC,kEAAkE;gBAClE,4DAA4D;gBAC5D,IAAI,sBAAsB,EAAE,CAAC;oBAC3B,MAAM,EAAE,CAAC,iBAAiB,CAAC,UAAU,CAAC;wBACpC,KAAK,EAAE,EAAE,WAAW,EAAE,OAAO,CAAC,EAAE,EAAE;qBACnC,CAAC,CAAC;gBACL,CAAC;gBAED,MAAM,EAAE,CAAC,iBAAiB,CAAC,UAAU,CAAC;oBACpC,IAAI,EAAE,KAAM,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;wBACxC,WAAW,EAAE,OAAO,CAAC,EAAE;wBACvB,IAAI,EAAE,QAAQ,CAAC,IAAI;wBACnB,QAAQ,EAAE,QAAQ,CAAC,QAAQ;wBAC3B,MAAM,EAAE,QAAQ,CAAC,MAAM;wBACvB,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,IAAI,CAAC,CAAC;wBACxC,OAAO,EAAE,QAAQ,CAAC,OAAO,IAAI,EAAE;wBAC/B,UAAU,EAAE,CAAC,QAAQ,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;4BACrD,GAAG,IAAI;4BACP,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC;yBAChC,CAAC,CAAC;wBACH,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC;qBAClC,CAAC,CAAC;iBACJ,CAAC,CAAC;YACL,CAAC;YAED,OAAO,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC;gBACzB,KAAK,EAAE,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,EAAE;gBACzB,IAAI,EAAE,EAAE,MAAM,EAAE,SAAS,CAAC,SAAS,EAAE;aACtC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QAEH,MAAM,UAAU,CAAC,sBAAsB,EAAE,SAAS,CAAC,SAAS,EAAE;YAC5D,IAAI,EAAE,SAAS,CAAC,IAAI;YACpB,aAAa,EAAE,KAAK,CAAC,SAAS,CAAC,MAAM;SACtC,CAAC,CAAC;QAEH,OAAO,SAAS,CAAC;IACnB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IACE,KAAK,YAAY,MAAM,CAAC,6BAA6B;YACrD,KAAK,CAAC,IAAI,KAAK,OAAO,EACtB,CAAC;YACD,MAAM,SAAS,GAAG,MAAM,MAAM,CAAC,SAAS,CAAC,UAAU,CAAC;gBAClD,KAAK,EAAE;oBACL,iBAAiB,EAAE;wBACjB,OAAO;wBACP,SAAS;qBACV;iBACF;aACF,CAAC,CAAC;YAEH,IAAI,SAAS,EAAE,CAAC;gBACd,MAAM,CAAC,IAAI,CAAC,8DAA8D,EAAE;oBAC1E,WAAW,EAAE,SAAS,CAAC,EAAE;oBACzB,OAAO;oBACP,SAAS;iBACV,CAAC,CAAC;gBAEH,IAAI,sBAAsB,IAAI,sBAAsB,KAAK,SAAS,CAAC,EAAE,EAAE,CAAC;oBACtE,MAAM,MAAM,CAAC,SAAS;yBACnB,MAAM,CAAC;wBACN,KAAK,EAAE,EAAE,EAAE,EAAE,sBAAsB,EAAE;wBACrC,IAAI,EAAE,EAAE,MAAM,EAAE,SAAS,CAAC,SAAS,EAAE;qBACtC,CAAC;yBACD,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;oBAE1B,MAAM,UAAU,CAAC,sBAAsB,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;gBAChE,CAAC;gBAED,OAAO,SAAS,CAAC;YACnB,CAAC;QACH,CAAC;QAED,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACvE,MAAM,mBAAmB,CAAC,sBAAsB,EAAE,OAAO,CAAC,CAAC;QAC3D,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC;AAED,KAAK,UAAU,mBAAmB,CAChC,WAA+B,EAC/B,YAAoB;IAEpB,IAAI,CAAC,WAAW;QAAE,OAAO;IAEzB,MAAM,MAAM,CAAC,SAAS;SACnB,MAAM,CAAC;QACN,KAAK,EAAE,EAAE,EAAE,EAAE,WAAW,EAAE;QAC1B,IAAI,EAAE,EAAE,MAAM,EAAE,SAAS,CAAC,MAAM,EAAE;KACnC,CAAC;SACD,KAAK,CAAC,CAAC,WAAW,EAAE,EAAE;QACrB,MAAM,CAAC,KAAK,CAAC,iDAAiD,EAAE;YAC9D,WAAW;YACX,KAAK,EACH,WAAW,YAAY,KAAK;gBAC1B,CAAC,CAAC,WAAW,CAAC,OAAO;gBACrB,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC;SAC1B,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEL,wEAAwE;IACxE,qEAAqE;IACrE,MAAM,CAAC,KAAK,CAAC,0CAA0C,EAAE;QACvD,WAAW;QACX,KAAK,EAAE,YAAY;KACpB,CAAC,CAAC;IAEH,MAAM,UAAU,CAAC,WAAW,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC;AAClD,CAAC","debug_id":"498d4f3f-e9a4-58fb-8e7a-ed49c5f116e7"}
1
+ {"version":3,"file":"pdfToWorksheet.js","sources":["pipelines/pdfToWorksheet.ts"],"sourceRoot":"/","sourcesContent":["/**\n * PDF URL → worksheet: send a remote PDF URL to structured inference,\n * then persist a Worksheet and WorksheetQuestion rows.\n *\n * Normally driven by a BullMQ worker, so the producer (e.g.\n * `createWorksheetFromUploadedPdf`) creates a placeholder Worksheet row up\n * front and passes its id in via `worksheetId`. The pipeline broadcasts\n * `Worksheet.status` transitions on the per-worksheet job Pusher channel\n * (`set-pending` / `set-completed` / `set-failed` / `set-cancelled`) — same\n * convention as `gradeWorksheet`.\n *\n * The legacy `pdfToWorksheet(gcsUrl, classId)` signature is preserved for\n * direct/synchronous callers (such as tests): when no `worksheetId` is\n * supplied the pipeline creates the row itself.\n */\nimport { z } from \"zod\";\nimport type { ResponseInput } from \"openai/resources/responses/responses.js\";\nimport { JobStatus, Prisma } from \"@prisma/client\";\nimport { prisma } from \"../lib/prisma.js\";\nimport { inference } from \"../utils/inference.js\";\nimport { logger } from \"../utils/logger.js\";\nimport { pusher, worksheetJobChannel } from \"../lib/pusher.js\";\n\n/** Restrict remote fetches to Google-hosted object URLs. */\nfunction isProbablyGoogleCloudUrl(url: string): boolean {\n try {\n const host = new URL(url).hostname;\n return (\n host.endsWith(\"storage.googleapis.com\") ||\n host.endsWith(\"googleusercontent.com\") ||\n host.includes(\"storage.cloud.google.com\")\n );\n } catch {\n return false;\n }\n}\n\nfunction getFilenameFromUrl(url: string): string {\n try {\n const parsed = new URL(url);\n const lastSegment = parsed.pathname.split(\"/\").filter(Boolean).pop();\n return lastSegment && lastSegment.includes(\".\") ? lastSegment : \"document.pdf\";\n } catch {\n return \"document.pdf\";\n }\n}\n\nfunction sanitizeUrlForLogs(url: string): string {\n try {\n const parsed = new URL(url);\n return `${parsed.origin}${parsed.pathname}`;\n } catch {\n return 'invalid-url';\n }\n}\n\n/**\n * Pusher event names mirror `gradeWorksheet`'s convention so clients can\n * share the same JobStatus → event-name mapping.\n */\nconst STATUS_EVENT_NAMES: Record<JobStatus, string> = {\n NOT_STARTED: 'set-not-started',\n PENDING: 'set-pending',\n COMPLETED: 'set-completed',\n FAILED: 'set-failed',\n CANCELLED: 'set-cancelled',\n};\n\n/**\n * Realtime payload for the per-worksheet job channel.\n *\n * On COMPLETED we ship the new title and question count so clients can\n * patch their card optimistically and side-step replica lag on the next\n * refetch. Error strings are intentionally NOT broadcast — they may carry\n * server internals; the full error stays on `RunJob.error` for ops only.\n */\nexport interface WorksheetJobStatusEvent {\n worksheetId: string;\n status: JobStatus;\n /** Filled on COMPLETED so clients can avoid a replica-lagged refetch. */\n name?: string;\n questionCount?: number;\n}\n\n/**\n * Fire-and-forget broadcast of a `Worksheet.status` transition. We never let\n * a Pusher outage break the actual pipeline: the persisted `Worksheet.status`\n * is the source of truth, the channel just nudges clients.\n */\nasync function emitStatus(\n worksheetId: string | undefined,\n status: JobStatus,\n extras: Pick<WorksheetJobStatusEvent, 'name' | 'questionCount'> = {},\n): Promise<void> {\n if (!worksheetId) return;\n\n try {\n await pusher.trigger(\n worksheetJobChannel(worksheetId),\n STATUS_EVENT_NAMES[status],\n { worksheetId, status, ...extras } satisfies WorksheetJobStatusEvent,\n );\n } catch (error) {\n logger.warn('pdfToWorksheet: failed to broadcast status update', {\n worksheetId,\n status,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n}\n\n/** LLM output shape (validated by inference). */\nconst worksheetDraftSchema = z.object({\n title: z.string(),\n questions: z\n .array(\n z.object({\n type: z.enum([\n \"MULTIPLE_CHOICE\",\n \"TRUE_FALSE\",\n \"SHORT_ANSWER\",\n \"LONG_ANSWER\",\n \"MATH_EXPRESSION\",\n \"ESSAY\",\n ]),\n question: z.string(),\n answer: z.string(),\n options: z\n .array(\n z.object({\n id: z.string(),\n text: z.string(),\n isCorrect: z.boolean(),\n })\n )\n .optional()\n .default([]),\n markScheme: z\n .array(\n z.object({\n id: z.string(),\n points: z.number().transform((value) => Math.round(value)),\n description: z.string(),\n })\n )\n .optional()\n .default([]),\n points: z.number().optional().default(0),\n order: z.number().transform((value) => Math.round(value)),\n })\n )\n .default([]),\n});\n\ntype WorksheetDraft = z.infer<typeof worksheetDraftSchema>;\n\nconst schemaInstructions = `Return ONLY valid JSON matching this schema:\n{\n \"title\": string,\n \"questions\": [\n {\n \"type\": \"MULTIPLE_CHOICE\" | \"TRUE_FALSE\" | \"SHORT_ANSWER\" | \"LONG_ANSWER\" | \"MATH_EXPRESSION\" | \"ESSAY\",\n \"question\": string,\n \"answer\": string,\n \"options\": [ { \"id\": string, \"text\": string, \"isCorrect\": boolean } ],\n \"markScheme\": [ { \"id\": string, \"points\": number, \"description\": string } ],\n \"points\": number,\n \"order\": number\n }\n ]\n}\nRules: \"questions\" is an array ([] if none). Non-MCQ \"options\" can be []. Use \"order\" 1,2,3,… in reading order. No markdown or extra keys.\nIf no answer is provided for a multiple-choice or true/false question, determine the answer from the options.`;\n\nexport interface PdfToWorksheetOptions {\n /**\n * Pre-existing Worksheet row id. When supplied, the pipeline fills in this\n * row instead of creating a new one and broadcasts progress on its job\n * channel. This is the path used by the BullMQ worker.\n */\n worksheetId?: string;\n /** Acting user for AI usage attribution. */\n userId?: string | null;\n}\n\n/**\n * Sends a PDF URL to the model for remote retrieval, extracts a worksheet via\n * structured inference, and inserts (or back-fills) worksheet + questions.\n *\n * When called with `options.worksheetId` the pipeline:\n * - keeps that row's id stable (the client already routed to it),\n * - drives `status` PENDING → COMPLETED / FAILED,\n * - fans out `set-*` Pusher events on `worksheetJobChannel`.\n */\nexport async function pdfToWorksheet(\n gcsUrl: string,\n classId: string,\n options: PdfToWorksheetOptions = {},\n) {\n const sourceUrl = sanitizeUrlForLogs(gcsUrl);\n const placeholderWorksheetId = options.worksheetId;\n\n await emitStatus(placeholderWorksheetId, JobStatus.PENDING);\n\n if (!isProbablyGoogleCloudUrl(gcsUrl)) {\n logger.warn(\"pdfToWorksheet: rejected unsupported PDF URL\", {\n sanitizedUrl: sourceUrl,\n });\n await markWorksheetFailed(\n placeholderWorksheetId,\n \"Only Google Cloud Storage URLs are supported\",\n );\n throw new Error(\"Only Google Cloud Storage URLs are supported\");\n }\n\n // Look for a previously-completed worksheet pointing at the same source PDF.\n // If there is one (and it's not the placeholder we were handed) we reuse it,\n // mark the placeholder CANCELLED so the editor stops spinning, and return\n // the canonical worksheet.\n const existingForSource = await prisma.worksheet.findUnique({\n where: { classId_sourceUrl: { classId, sourceUrl } },\n });\n\n if (existingForSource && existingForSource.id !== placeholderWorksheetId) {\n logger.info(\"pdfToWorksheet: reusing existing worksheet for source PDF\", {\n worksheetId: existingForSource.id,\n classId,\n sourceUrl,\n });\n\n if (placeholderWorksheetId) {\n await prisma.worksheet\n .update({\n where: { id: placeholderWorksheetId },\n data: { status: JobStatus.CANCELLED },\n })\n .catch(() => undefined);\n\n await emitStatus(placeholderWorksheetId, JobStatus.CANCELLED);\n }\n\n return existingForSource;\n }\n\n const filename = getFilenameFromUrl(gcsUrl);\n const prompt = `Read the attached PDF and convert it into a single worksheet JSON object.\\n\\n${schemaInstructions}`;\n\n // Force Responses API input types so we can pass a remote PDF via `file_url`.\n const input: ResponseInput = [\n {\n role: \"user\",\n content: [\n { type: \"input_text\", text: prompt },\n { type: \"input_file\", file_url: gcsUrl },\n ],\n },\n ];\n\n let draft: WorksheetDraft | null;\n try {\n draft = await inference<WorksheetDraft>(\n input,\n worksheetDraftSchema,\n {\n reason: 'pdf_to_worksheet_draft',\n metadata: {\n classId,\n filename,\n },\n attribution: { userId: options.userId, classId },\n }\n );\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n await markWorksheetFailed(placeholderWorksheetId, message);\n throw error;\n }\n\n if (!draft) {\n logger.warn(\"pdfToWorksheet: no model output\", { filename });\n await markWorksheetFailed(\n placeholderWorksheetId,\n \"Model returned no parseable worksheet output\",\n );\n return null;\n }\n\n try {\n const worksheet = await prisma.$transaction(async (tx) => {\n const created = placeholderWorksheetId\n ? await tx.worksheet.update({\n where: { id: placeholderWorksheetId },\n data: {\n classId,\n name: draft!.title,\n sourceUrl,\n status: JobStatus.PENDING,\n },\n })\n : await tx.worksheet.create({\n data: {\n classId,\n name: draft!.title,\n sourceUrl,\n status: JobStatus.PENDING,\n },\n });\n\n if (draft!.questions.length > 0) {\n // When re-running against an existing placeholder, drop any prior\n // questions so we don't accumulate duplicates from retries.\n if (placeholderWorksheetId) {\n await tx.worksheetQuestion.deleteMany({\n where: { worksheetId: created.id },\n });\n }\n\n await tx.worksheetQuestion.createMany({\n data: draft!.questions.map((question) => ({\n worksheetId: created.id,\n type: question.type,\n question: question.question,\n answer: question.answer,\n points: Math.round(question.points ?? 0),\n options: question.options ?? [],\n markScheme: (question.markScheme ?? []).map((item) => ({\n ...item,\n points: Math.round(item.points),\n })),\n order: Math.round(question.order),\n })),\n });\n }\n\n return tx.worksheet.update({\n where: { id: created.id },\n data: { status: JobStatus.COMPLETED },\n });\n });\n\n await emitStatus(placeholderWorksheetId, JobStatus.COMPLETED, {\n name: worksheet.name,\n questionCount: draft.questions.length,\n });\n\n return worksheet;\n } catch (error) {\n if (\n error instanceof Prisma.PrismaClientKnownRequestError &&\n error.code === \"P2002\"\n ) {\n const worksheet = await prisma.worksheet.findUnique({\n where: {\n classId_sourceUrl: {\n classId,\n sourceUrl,\n },\n },\n });\n\n if (worksheet) {\n logger.info(\"pdfToWorksheet: reused worksheet created by concurrent retry\", {\n worksheetId: worksheet.id,\n classId,\n sourceUrl,\n });\n\n if (placeholderWorksheetId && placeholderWorksheetId !== worksheet.id) {\n await prisma.worksheet\n .update({\n where: { id: placeholderWorksheetId },\n data: { status: JobStatus.CANCELLED },\n })\n .catch(() => undefined);\n\n await emitStatus(placeholderWorksheetId, JobStatus.CANCELLED);\n }\n\n return worksheet;\n }\n }\n\n const message = error instanceof Error ? error.message : String(error);\n await markWorksheetFailed(placeholderWorksheetId, message);\n throw error;\n }\n}\n\nasync function markWorksheetFailed(\n worksheetId: string | undefined,\n errorMessage: string,\n) {\n if (!worksheetId) return;\n\n await prisma.worksheet\n .update({\n where: { id: worksheetId },\n data: { status: JobStatus.FAILED },\n })\n .catch((updateError) => {\n logger.error('pdfToWorksheet: failed to mark worksheet FAILED', {\n worksheetId,\n error:\n updateError instanceof Error\n ? updateError.message\n : String(updateError),\n });\n });\n\n // The full error is logged + persisted on `RunJob.error`; the broadcast\n // intentionally carries no error payload to avoid leaking internals.\n logger.error('pdfToWorksheet: marking worksheet FAILED', {\n worksheetId,\n error: errorMessage,\n });\n\n await emitStatus(worksheetId, JobStatus.FAILED);\n}\n"],"names":[],"mappings":"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","debug_id":"d76f2a44-92c7-5185-9060-1b4bf66db0e1"}