@studious-lms/server 1.9.0 → 1.12.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 (163) hide show
  1. package/.env.example +3 -0
  2. package/dist/index.js +40 -4
  3. package/dist/index.js.map +1 -1
  4. package/dist/lib/config/cors.d.ts.map +1 -1
  5. package/dist/lib/config/cors.js +7 -2
  6. package/dist/lib/config/cors.js.map +1 -1
  7. package/dist/lib/config/env.d.ts +8 -0
  8. package/dist/lib/config/env.d.ts.map +1 -1
  9. package/dist/lib/config/env.js +5 -2
  10. package/dist/lib/config/env.js.map +1 -1
  11. package/dist/lib/pusher.d.ts +2 -1
  12. package/dist/lib/pusher.d.ts.map +1 -1
  13. package/dist/lib/pusher.js +4 -3
  14. package/dist/lib/pusher.js.map +1 -1
  15. package/dist/models/agenda.d.ts +6 -0
  16. package/dist/models/agenda.d.ts.map +1 -1
  17. package/dist/models/announcement.d.ts +3 -0
  18. package/dist/models/announcement.d.ts.map +1 -1
  19. package/dist/models/assignment.d.ts +61 -0
  20. package/dist/models/assignment.d.ts.map +1 -1
  21. package/dist/models/auth.d.ts +6 -0
  22. package/dist/models/auth.d.ts.map +1 -1
  23. package/dist/models/class.d.ts +18 -0
  24. package/dist/models/class.d.ts.map +1 -1
  25. package/dist/models/class.js +5 -2
  26. package/dist/models/class.js.map +1 -1
  27. package/dist/models/comment.d.ts +6 -0
  28. package/dist/models/comment.d.ts.map +1 -1
  29. package/dist/models/event.d.ts +12 -0
  30. package/dist/models/event.d.ts.map +1 -1
  31. package/dist/models/file.d.ts +38 -0
  32. package/dist/models/file.d.ts.map +1 -1
  33. package/dist/models/folder.d.ts +6 -0
  34. package/dist/models/folder.d.ts.map +1 -1
  35. package/dist/models/labChat.d.ts +6 -0
  36. package/dist/models/labChat.d.ts.map +1 -1
  37. package/dist/models/newtonChat.d.ts +3 -0
  38. package/dist/models/newtonChat.d.ts.map +1 -1
  39. package/dist/models/school.d.ts +3 -0
  40. package/dist/models/school.d.ts.map +1 -1
  41. package/dist/models/section.d.ts +3 -0
  42. package/dist/models/section.d.ts.map +1 -1
  43. package/dist/models/worksheet.d.ts +58 -0
  44. package/dist/models/worksheet.d.ts.map +1 -1
  45. package/dist/models/worksheet.js +26 -2
  46. package/dist/models/worksheet.js.map +1 -1
  47. package/dist/routers/_app.d.ts +6400 -4078
  48. package/dist/routers/_app.d.ts.map +1 -1
  49. package/dist/routers/_app.js +6 -2
  50. package/dist/routers/_app.js.map +1 -1
  51. package/dist/routers/admin.d.ts +190 -0
  52. package/dist/routers/admin.d.ts.map +1 -0
  53. package/dist/routers/admin.js +50 -0
  54. package/dist/routers/admin.js.map +1 -0
  55. package/dist/routers/agenda.d.ts +6 -0
  56. package/dist/routers/agenda.d.ts.map +1 -1
  57. package/dist/routers/announcement.d.ts +2 -0
  58. package/dist/routers/announcement.d.ts.map +1 -1
  59. package/dist/routers/assignment.d.ts +50 -0
  60. package/dist/routers/assignment.d.ts.map +1 -1
  61. package/dist/routers/class.d.ts +31 -0
  62. package/dist/routers/class.d.ts.map +1 -1
  63. package/dist/routers/class.js +5 -2
  64. package/dist/routers/class.js.map +1 -1
  65. package/dist/routers/comment.d.ts +2 -0
  66. package/dist/routers/comment.d.ts.map +1 -1
  67. package/dist/routers/discussion.d.ts +823 -0
  68. package/dist/routers/discussion.d.ts.map +1 -0
  69. package/dist/routers/discussion.js +151 -0
  70. package/dist/routers/discussion.js.map +1 -0
  71. package/dist/routers/event.d.ts +3 -0
  72. package/dist/routers/event.d.ts.map +1 -1
  73. package/dist/routers/file.d.ts +4 -0
  74. package/dist/routers/file.d.ts.map +1 -1
  75. package/dist/routers/school.d.ts +6 -0
  76. package/dist/routers/school.d.ts.map +1 -1
  77. package/dist/routers/studentProgress.d.ts +45 -0
  78. package/dist/routers/studentProgress.d.ts.map +1 -1
  79. package/dist/routers/studentProgress.js +12 -3
  80. package/dist/routers/studentProgress.js.map +1 -1
  81. package/dist/routers/worksheet.d.ts +3 -0
  82. package/dist/routers/worksheet.d.ts.map +1 -1
  83. package/dist/seedDatabase.d.ts.map +1 -1
  84. package/dist/seedDatabase.js +1490 -1307
  85. package/dist/seedDatabase.js.map +1 -1
  86. package/dist/services/adminInstitution.d.ts +102 -0
  87. package/dist/services/adminInstitution.d.ts.map +1 -0
  88. package/dist/services/adminInstitution.js +117 -0
  89. package/dist/services/adminInstitution.js.map +1 -0
  90. package/dist/services/adminRunJob.d.ts +45 -0
  91. package/dist/services/adminRunJob.d.ts.map +1 -0
  92. package/dist/services/adminRunJob.js +41 -0
  93. package/dist/services/adminRunJob.js.map +1 -0
  94. package/dist/services/agenda.d.ts +6 -0
  95. package/dist/services/agenda.d.ts.map +1 -1
  96. package/dist/services/assignment.d.ts +50 -0
  97. package/dist/services/assignment.d.ts.map +1 -1
  98. package/dist/services/assignment.js +8 -3
  99. package/dist/services/assignment.js.map +1 -1
  100. package/dist/services/class.d.ts +31 -0
  101. package/dist/services/class.d.ts.map +1 -1
  102. package/dist/services/class.js +14 -2
  103. package/dist/services/class.js.map +1 -1
  104. package/dist/services/comment.d.ts +2 -0
  105. package/dist/services/comment.d.ts.map +1 -1
  106. package/dist/services/discussion.d.ts +762 -0
  107. package/dist/services/discussion.d.ts.map +1 -0
  108. package/dist/services/discussion.js +625 -0
  109. package/dist/services/discussion.js.map +1 -0
  110. package/dist/services/event.d.ts +3 -0
  111. package/dist/services/event.d.ts.map +1 -1
  112. package/dist/services/file.d.ts +8 -0
  113. package/dist/services/file.d.ts.map +1 -1
  114. package/dist/services/school.d.ts +6 -0
  115. package/dist/services/school.d.ts.map +1 -1
  116. package/dist/services/studentProgress.d.ts +94 -0
  117. package/dist/services/studentProgress.d.ts.map +1 -1
  118. package/dist/services/studentProgress.js +116 -3
  119. package/dist/services/studentProgress.js.map +1 -1
  120. package/dist/services/studentProgressInferenceCache.d.ts +16 -0
  121. package/dist/services/studentProgressInferenceCache.d.ts.map +1 -0
  122. package/dist/services/studentProgressInferenceCache.js +43 -0
  123. package/dist/services/studentProgressInferenceCache.js.map +1 -0
  124. package/dist/services/worksheet.d.ts +3 -0
  125. package/dist/services/worksheet.d.ts.map +1 -1
  126. package/dist/services/worksheet.js +5 -5
  127. package/dist/services/worksheet.js.map +1 -1
  128. package/dist/trpc.d.ts +12 -0
  129. package/dist/trpc.d.ts.map +1 -1
  130. package/dist/trpc.js +16 -3
  131. package/dist/trpc.js.map +1 -1
  132. package/dist/utils/adminPanelAccess.d.ts +8 -0
  133. package/dist/utils/adminPanelAccess.d.ts.map +1 -0
  134. package/dist/utils/adminPanelAccess.js +44 -0
  135. package/dist/utils/adminPanelAccess.js.map +1 -0
  136. package/package.json +1 -1
  137. package/prisma/migrations/20260510183000_add_discussions/migration.sql +151 -0
  138. package/prisma/schema.prisma +97 -0
  139. package/src/index.ts +39 -2
  140. package/src/lib/config/cors.ts +5 -0
  141. package/src/lib/config/env.ts +3 -0
  142. package/src/lib/pusher.ts +2 -1
  143. package/src/models/class.ts +3 -0
  144. package/src/models/worksheet.ts +28 -0
  145. package/src/routers/_app.ts +4 -0
  146. package/src/routers/admin.ts +70 -0
  147. package/src/routers/class.ts +3 -0
  148. package/src/routers/discussion.ts +205 -0
  149. package/src/routers/studentProgress.ts +21 -0
  150. package/src/seedDatabase.ts +1713 -1361
  151. package/src/services/adminInstitution.ts +140 -0
  152. package/src/services/adminRunJob.ts +46 -0
  153. package/src/services/assignment.ts +11 -1
  154. package/src/services/class.ts +18 -0
  155. package/src/services/discussion.ts +701 -0
  156. package/src/services/studentProgress.ts +274 -11
  157. package/src/services/studentProgressInferenceCache.ts +64 -0
  158. package/src/services/worksheet.ts +4 -2
  159. package/src/trpc.ts +15 -0
  160. package/src/utils/adminPanelAccess.ts +46 -0
  161. package/tests/integration/routers/discussion.test.ts +253 -0
  162. package/tests/unit/services/discussion.test.ts +356 -0
  163. package/tests/unit/services/studentProgress.test.ts +168 -2
@@ -1 +1 @@
1
- {"version":3,"file":"studentProgress.js","sources":["services/studentProgress.ts"],"sourceRoot":"/","sourcesContent":["/**\n * Student progress service - assignment recommendations and progress chat.\n */\nimport { TRPCError } from \"@trpc/server\";\nimport type { AssignmentType } from \"@prisma/client\";\nimport { prisma } from \"../lib/prisma.js\";\nimport { isTeacherInClass } from \"../models/class.js\";\nimport { inference } from \"../utils/inference.js\";\nimport { logger } from \"../utils/logger.js\";\n\ntype ProgressChatMessage = {\n role: \"user\" | \"assistant\";\n content: string;\n};\n\ntype GradeSubmission = {\n id: string;\n gradeReceived: number | null;\n submitted: boolean | null;\n returned: boolean | null;\n submittedAt: Date | null;\n recommendationState: \"NONE\" | \"OPEN\" | \"ASSIGNED\" | \"DISMISSED\";\n targetedAssignmentId: string | null;\n assignment: {\n id: string;\n title: string;\n dueDate: Date;\n maxGrade: number | null;\n weight: number;\n type: AssignmentType;\n section: { id: string; name: string } | null;\n };\n};\n\ntype ProgressStudent = {\n id: string;\n username: string;\n profile: { displayName: string | null } | null;\n};\n\ntype RecommendationType =\n | \"missing\"\n | \"low_score\"\n | \"awaiting_feedback\";\n\nfunction calculatePercentage(submission: GradeSubmission) {\n if (submission.gradeReceived == null || !submission.assignment.maxGrade) {\n return null;\n }\n\n return (\n Math.round(\n (submission.gradeReceived / submission.assignment.maxGrade) * 1000,\n ) / 10\n );\n}\n\nfunction calculateTrend(submissions: GradeSubmission[]) {\n const graded = submissions\n .filter(\n (submission) =>\n submission.gradeReceived != null && submission.assignment.maxGrade,\n )\n .sort((a, b) => {\n const aTime = a.submittedAt?.getTime() ?? a.assignment.dueDate.getTime();\n const bTime = b.submittedAt?.getTime() ?? b.assignment.dueDate.getTime();\n return aTime - bTime;\n })\n .map((submission) => calculatePercentage(submission))\n .filter((value): value is number => value != null);\n\n if (graded.length < 2) return 0;\n\n const midpoint = Math.floor(graded.length / 2);\n const first = graded.slice(0, midpoint);\n const second = graded.slice(midpoint);\n const average = (values: number[]) =>\n values.reduce((sum, value) => sum + value, 0) / values.length;\n\n return Math.round((average(second) - average(first)) * 10) / 10;\n}\n\nfunction getOverallGrade(submissions: GradeSubmission[]) {\n let totalWeighted = 0;\n let totalWeight = 0;\n\n for (const submission of submissions) {\n if (\n submission.gradeReceived != null &&\n submission.assignment.maxGrade &&\n submission.assignment.weight\n ) {\n totalWeighted +=\n (submission.gradeReceived / submission.assignment.maxGrade) *\n submission.assignment.weight;\n totalWeight += submission.assignment.weight;\n }\n }\n\n return totalWeight > 0\n ? Math.round((totalWeighted / totalWeight) * 1000) / 10\n : null;\n}\n\nfunction normalizeAssignmentType(\n assignmentType: AssignmentType,\n): AssignmentType {\n if (assignmentType === \"TEST\" || assignmentType === \"QUIZ\") {\n return \"HOMEWORK\";\n }\n\n return assignmentType;\n}\n\nfunction buildSuggestedAssignment(\n recommendationType: RecommendationType,\n submission: GradeSubmission,\n displayName: string,\n percentage: number | null,\n) {\n const sourceTitle = submission.assignment.title;\n const section = submission.assignment.section?.name;\n const titleByType: Record<RecommendationType, string> = {\n missing: `Make-up: ${sourceTitle}`,\n low_score: `Targeted practice: ${sourceTitle}`,\n awaiting_feedback: `Revision follow-up: ${sourceTitle}`,\n };\n const leadByType: Record<RecommendationType, string> = {\n missing: `${displayName} missed the original assignment and needs a focused make-up task on the same material.`,\n low_score:\n percentage != null\n ? `${displayName} scored ${percentage}% and needs targeted reinforcement on the underlying concepts.`\n : `${displayName} needs targeted reinforcement on the underlying concepts.`,\n awaiting_feedback:\n `${displayName} has work in progress here, so a short follow-up can keep momentum once feedback lands.`,\n };\n\n return {\n title: titleByType[recommendationType],\n instructions: [\n leadByType[recommendationType],\n `Source assignment: ${sourceTitle}.`,\n section ? `Section: ${section}.` : null,\n \"Keep the work concise and focused on the specific area of concern.\",\n ]\n .filter((line): line is string => Boolean(line))\n .join(\"\\n\\n\"),\n type: normalizeAssignmentType(submission.assignment.type),\n maxGrade: submission.assignment.maxGrade ?? 100,\n weight: submission.assignment.weight || 1,\n graded: true,\n sectionId: submission.assignment.section?.id ?? null,\n sourceAssignmentId: submission.assignment.id,\n };\n}\n\nfunction buildRecommendationCandidate(\n submission: GradeSubmission,\n displayName: string,\n now: Date,\n) {\n if (\n submission.recommendationState === \"ASSIGNED\" ||\n submission.recommendationState === \"DISMISSED\"\n ) {\n return null;\n }\n\n const percentage = calculatePercentage(submission);\n const isMissing =\n !submission.submitted &&\n submission.assignment.dueDate.getTime() < now.getTime();\n const isLowScore = percentage != null && percentage < 70;\n const isUnreturned =\n Boolean(submission.submitted) &&\n submission.gradeReceived == null &&\n !submission.returned;\n const reasons: string[] = [];\n let priorityScore = 0;\n let recommendationType: RecommendationType | null = null;\n\n if (isMissing) {\n reasons.push(\"Missing past-due work\");\n priorityScore += 100;\n recommendationType = \"missing\";\n }\n if (isLowScore) {\n reasons.push(`Scored ${percentage}%`);\n priorityScore += 85 - (percentage ?? 0);\n recommendationType ??= \"low_score\";\n }\n if (isUnreturned) {\n reasons.push(\"Awaiting grade or feedback\");\n priorityScore += 15;\n recommendationType ??= \"awaiting_feedback\";\n }\n\n if (priorityScore <= 0 || !recommendationType) {\n return null;\n }\n\n return {\n assignmentId: submission.assignment.id,\n submissionId: submission.id,\n title: submission.assignment.title,\n type: submission.assignment.type,\n sectionId: submission.assignment.section?.id ?? null,\n sectionName: submission.assignment.section?.name ?? null,\n dueDate: submission.assignment.dueDate,\n gradeReceived: submission.gradeReceived,\n maxGrade: submission.assignment.maxGrade,\n percentage,\n submitted: Boolean(submission.submitted),\n returned: Boolean(submission.returned),\n recommendationType,\n reasons,\n priorityScore,\n suggestedAssignment: buildSuggestedAssignment(\n recommendationType,\n submission,\n displayName,\n percentage,\n ),\n };\n}\n\nfunction summarizeStudentRecommendations(\n student: ProgressStudent,\n submissions: GradeSubmission[],\n) {\n const now = new Date();\n const displayName = student.profile?.displayName ?? student.username;\n const recommendationCandidates = submissions\n .map((submission) =>\n buildRecommendationCandidate(submission, displayName, now),\n )\n .filter(\n (\n candidate,\n ): candidate is NonNullable<\n ReturnType<typeof buildRecommendationCandidate>\n > => candidate != null,\n )\n .sort((a, b) => b.priorityScore - a.priorityScore);\n\n const recommendations = recommendationCandidates\n .slice(0, 5)\n .map(({ priorityScore, ...candidate }) => candidate);\n\n const gradedPercentages = submissions\n .map(calculatePercentage)\n .filter((value): value is number => value != null);\n const lowScoreCount = gradedPercentages.filter(\n (percentage) => percentage < 70,\n ).length;\n const missingCount = submissions.filter(\n (submission) =>\n !submission.submitted &&\n submission.assignment.dueDate.getTime() < now.getTime(),\n ).length;\n const trend = calculateTrend(submissions);\n const overallGrade = getOverallGrade(submissions);\n\n const nextSteps = [\n missingCount > 0\n ? `Prioritize ${missingCount} missing assignment${missingCount === 1 ? \"\" : \"s\"}.`\n : null,\n lowScoreCount > 0\n ? `Review ${lowScoreCount} low-scoring assignment${lowScoreCount === 1 ? \"\" : \"s\"} before introducing new material.`\n : null,\n trend < -5\n ? \"Schedule a check-in because recent performance is trending down.\"\n : null,\n recommendationCandidates.length === 0\n ? \"No urgent assignment issues detected from the available grades.\"\n : null,\n ].filter((step): step is string => Boolean(step));\n\n return {\n student: {\n id: student.id,\n username: student.username,\n displayName,\n },\n summary: {\n overallGrade,\n trend,\n completedAssignments: submissions.filter((submission) =>\n Boolean(submission.submitted),\n ).length,\n totalAssignments: submissions.length,\n missingCount,\n lowScoreCount,\n },\n recommendations,\n recommendationCandidates,\n nextSteps,\n priorityScore: recommendationCandidates[0]?.priorityScore ?? 0,\n };\n}\n\nasync function syncRecommendationStates(\n submissions: GradeSubmission[],\n recommendationCandidates: Array<\n NonNullable<ReturnType<typeof buildRecommendationCandidate>>\n >,\n) {\n const candidateSubmissionIds = new Set(\n recommendationCandidates.map((candidate) => candidate.submissionId),\n );\n const updates: Promise<unknown>[] = [];\n\n for (const submission of submissions) {\n const shouldBeOpen = candidateSubmissionIds.has(submission.id);\n\n if (shouldBeOpen && submission.recommendationState === \"NONE\") {\n updates.push(\n prisma.submission.update({\n where: { id: submission.id },\n data: {\n recommendationState: \"OPEN\",\n recommendationUpdatedAt: new Date(),\n },\n }),\n );\n continue;\n }\n\n if (!shouldBeOpen && submission.recommendationState === \"OPEN\") {\n updates.push(\n prisma.submission.update({\n where: { id: submission.id },\n data: {\n recommendationState: \"NONE\",\n recommendationUpdatedAt: new Date(),\n },\n }),\n );\n }\n }\n\n if (updates.length) {\n await Promise.all(updates);\n }\n}\n\nasync function summarizeStudentRecommendationsWithCases(\n student: ProgressStudent,\n submissions: GradeSubmission[],\n) {\n const summary = summarizeStudentRecommendations(student, submissions);\n await syncRecommendationStates(submissions, summary.recommendationCandidates);\n return summary;\n}\n\nasync function loadStudentProgressContext(\n viewerId: string,\n classId: string,\n studentId: string,\n) {\n const isTeacher = await isTeacherInClass(classId, viewerId);\n if (viewerId !== studentId && !isTeacher) {\n throw new TRPCError({\n code: \"UNAUTHORIZED\",\n message: \"You can only view your own progress\",\n });\n }\n\n const [classData, student, submissions] = await Promise.all([\n prisma.class.findUnique({\n where: { id: classId },\n select: { id: true, name: true, subject: true },\n }),\n prisma.user.findFirst({\n where: {\n id: studentId,\n studentIn: { some: { id: classId } },\n },\n select: {\n id: true,\n username: true,\n profile: { select: { displayName: true } },\n },\n }),\n prisma.submission.findMany({\n where: {\n studentId,\n assignment: { classId, graded: true },\n },\n include: {\n assignment: {\n select: {\n id: true,\n title: true,\n dueDate: true,\n maxGrade: true,\n weight: true,\n type: true,\n section: { select: { id: true, name: true } },\n },\n },\n },\n orderBy: { assignment: { dueDate: \"asc\" } },\n }),\n ]);\n\n if (!classData) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Class not found\" });\n }\n\n if (!student) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Student not found in this class\",\n });\n }\n\n return { classData, student, submissions };\n}\n\nexport async function getStudentProgressRecommendations(\n viewerId: string,\n classId: string,\n studentId: string,\n) {\n const { student, submissions } = await loadStudentProgressContext(\n viewerId,\n classId,\n studentId,\n );\n\n const summary = await summarizeStudentRecommendationsWithCases(student, submissions);\n const { recommendationCandidates: _recommendationCandidates, ...summaryResponse } =\n summary;\n return {\n student: summaryResponse.student,\n summary: summaryResponse.summary,\n recommendations: summaryResponse.recommendations,\n nextSteps: summaryResponse.nextSteps,\n };\n}\n\nexport async function getClassProgressRecommendations(\n viewerId: string,\n classId: string,\n) {\n const teacherInClass = await isTeacherInClass(classId, viewerId);\n if (!teacherInClass) {\n throw new TRPCError({\n code: \"UNAUTHORIZED\",\n message: \"Only teachers can view class-wide recommendations\",\n });\n }\n\n const classData = await prisma.class.findUnique({\n where: { id: classId },\n select: {\n id: true,\n name: true,\n subject: true,\n students: {\n select: {\n id: true,\n username: true,\n profile: { select: { displayName: true } },\n submissions: {\n where: {\n assignment: { classId, graded: true },\n },\n include: {\n assignment: {\n select: {\n id: true,\n title: true,\n dueDate: true,\n maxGrade: true,\n weight: true,\n type: true,\n section: { select: { id: true, name: true } },\n },\n },\n },\n orderBy: { assignment: { dueDate: \"asc\" } },\n },\n },\n },\n },\n });\n\n if (!classData) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Class not found\" });\n }\n\n const students = (\n await Promise.all(\n classData.students.map((student) =>\n summarizeStudentRecommendationsWithCases(student, student.submissions),\n ),\n )\n )\n .map(({ recommendationCandidates: _recommendationCandidates, ...student }) => student)\n .filter((student) => student.recommendations.length > 0)\n .sort((a, b) => b.priorityScore - a.priorityScore);\n\n const totalRecommendations = students.reduce(\n (sum, student) => sum + student.recommendations.length,\n 0,\n );\n const studentsWithConcerns = students.length;\n const totalMissingAssignments = students.reduce(\n (sum, student) => sum + student.summary.missingCount,\n 0,\n );\n const totalLowScoreAssignments = students.reduce(\n (sum, student) => sum + student.summary.lowScoreCount,\n 0,\n );\n\n return {\n class: {\n id: classData.id,\n name: classData.name,\n subject: classData.subject,\n },\n summary: {\n studentsWithConcerns,\n totalRecommendations,\n totalMissingAssignments,\n totalLowScoreAssignments,\n },\n students: students.map(({ priorityScore, ...student }) => student),\n };\n}\n\nexport async function dismissStudentRecommendation(\n viewerId: string,\n input: {\n classId: string;\n studentId: string;\n submissionId: string;\n },\n) {\n const teacherInClass = await isTeacherInClass(input.classId, viewerId);\n if (!teacherInClass) {\n throw new TRPCError({\n code: \"UNAUTHORIZED\",\n message: \"Only teachers can dismiss recommendations\",\n });\n }\n\n const submission = await prisma.submission.findFirst({\n where: {\n id: input.submissionId,\n studentId: input.studentId,\n assignment: {\n classId: input.classId,\n },\n },\n select: { id: true },\n });\n\n if (!submission) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Submission not found for this student in this class\",\n });\n }\n\n await prisma.submission.update({\n where: { id: input.submissionId },\n data: {\n recommendationState: \"DISMISSED\",\n recommendationUpdatedAt: new Date(),\n },\n });\n\n return { success: true };\n}\n\nfunction buildProgressSummary(submissions: GradeSubmission[]) {\n return submissions.map((submission) => ({\n title: submission.assignment.title,\n type: submission.assignment.type,\n dueDate: submission.assignment.dueDate.toISOString(),\n submitted: Boolean(submission.submitted),\n returned: Boolean(submission.returned),\n gradeReceived: submission.gradeReceived,\n maxGrade: submission.assignment.maxGrade,\n percentage: calculatePercentage(submission),\n section: submission.assignment.section?.name ?? null,\n }));\n}\n\nexport async function chatAboutStudentProgress(\n viewerId: string,\n input: {\n classId: string;\n studentId: string;\n message: string;\n history?: ProgressChatMessage[];\n },\n) {\n const { classData, student, submissions } = await loadStudentProgressContext(\n viewerId,\n input.classId,\n input.studentId,\n );\n\n const displayName = student.profile?.displayName ?? student.username;\n const summary = {\n overallGrade: getOverallGrade(submissions),\n trend: calculateTrend(submissions),\n assignments: buildProgressSummary(submissions),\n };\n\n const messages = [\n {\n role: \"system\" as const,\n content:\n \"You are an educational progress assistant for teachers and students. Use only the provided class and grade context. Be concise, specific, supportive, and avoid fabricating grades or assignments.\",\n },\n {\n role: \"user\" as const,\n content: JSON.stringify({\n class: classData,\n student: { id: student.id, username: student.username, displayName },\n progress: summary,\n }),\n },\n ...(input.history ?? []).slice(-8).map((message) => ({\n role: message.role,\n content: message.content,\n })),\n { role: \"user\" as const, content: input.message },\n ];\n\n try {\n const response = await inference<string>(messages, undefined, {\n reason: \"student_progress_chat\",\n metadata: { viewerId, classId: input.classId, studentId: input.studentId },\n });\n if (typeof response !== \"string\" || response.trim().length === 0) {\n throw new Error(\"Student progress chat returned an empty response\");\n }\n return { message: response, isFallback: false };\n } catch (error) {\n logger.error(\"Failed to generate student progress chat response\", {\n error,\n classId: input.classId,\n studentId: input.studentId,\n });\n\n const overall =\n summary.overallGrade == null\n ? \"not enough graded work\"\n : `${summary.overallGrade}%`;\n const missingItems = summary.assignments.filter(\n (assignment) =>\n !assignment.submitted &&\n new Date(assignment.dueDate).getTime() < Date.now(),\n ).length;\n const lowScores = summary.assignments.filter(\n (assignment) =>\n assignment.percentage != null && assignment.percentage < 70,\n ).length;\n const awaitingFeedback = summary.assignments.filter(\n (assignment) =>\n assignment.submitted &&\n assignment.gradeReceived == null &&\n !assignment.returned,\n ).length;\n\n const fallbackParts = [\n `${displayName}'s current overall grade is ${overall}.`,\n missingItems > 0\n ? `${missingItems} assignment${missingItems === 1 ? \"\" : \"s\"} ${missingItems === 1 ? \"appears\" : \"appear\"} overdue.`\n : null,\n lowScores > 0\n ? `${lowScores} graded assignment${lowScores === 1 ? \" is\" : \"s are\"} below 70%.`\n : null,\n awaitingFeedback > 0\n ? `${awaitingFeedback} assignment${awaitingFeedback === 1 ? \" is\" : \"s are\"} still awaiting feedback.`\n : null,\n summary.trend < -5\n ? \"Recent performance is trending down, so a check-in would be reasonable.\"\n : \"Performance looks relatively steady from the available data.\",\n ].filter((part): part is string => Boolean(part));\n\n return {\n message: fallbackParts.join(\" \"),\n isFallback: true,\n };\n }\n}\n"],"names":[],"mappings":"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","debug_id":"9369c0fb-4b1a-5b05-8e92-03f34d46226c"}
1
+ {"version":3,"file":"studentProgress.js","sources":["services/studentProgress.ts"],"sourceRoot":"/","sourcesContent":["/**\n * Student progress service - assignment recommendations and progress chat.\n */\nimport { TRPCError } from \"@trpc/server\";\nimport type { AssignmentType } from \"@prisma/client\";\nimport { z } from \"zod\";\nimport { prisma } from \"../lib/prisma.js\";\nimport { isTeacherInClass } from \"../models/class.js\";\nimport { inference } from \"../utils/inference.js\";\nimport { logger } from \"../utils/logger.js\";\nimport {\n getStudentProgressInferenceCacheHit,\n setStudentProgressInferenceCache,\n} from \"./studentProgressInferenceCache.js\";\n\ntype ProgressChatMessage = {\n role: \"user\" | \"assistant\";\n content: string;\n};\n\ntype GradeSubmission = {\n id: string;\n gradeReceived: number | null;\n submitted: boolean | null;\n returned: boolean | null;\n submittedAt: Date | null;\n recommendationState: \"NONE\" | \"OPEN\" | \"ASSIGNED\" | \"DISMISSED\";\n targetedAssignmentId: string | null;\n assignment: {\n id: string;\n title: string;\n dueDate: Date;\n maxGrade: number | null;\n weight: number;\n type: AssignmentType;\n section: { id: string; name: string } | null;\n };\n};\n\ntype ProgressStudent = {\n id: string;\n username: string;\n profile: { displayName: string | null } | null;\n};\n\nconst insightItemSchema = z.object({\n label: z.string(),\n evidence: z.string(),\n});\n\nconst studentProgressInsightsSchema = z.object({\n summary: z.string(),\n strengths: z.array(insightItemSchema).min(2).max(4),\n growth: z.array(insightItemSchema).min(2).max(4),\n talkingPoints: z.array(z.string()).min(3).max(5),\n});\n\nconst studentProgressForecastSchema = z.object({\n forecastLow: z.number().min(0).max(100),\n forecastHigh: z.number().min(0).max(100),\n risk: z.enum([\"on_track\", \"watch\", \"at_risk\"]),\n reason: z.string(),\n});\n\nconst studentProgressFocusSchema = z.object({\n focusItems: z.array(z.string()).min(3).max(5),\n});\n\nexport type StudentProgressInsights = z.infer<\n typeof studentProgressInsightsSchema\n>;\nexport type StudentProgressForecast = z.infer<\n typeof studentProgressForecastSchema\n>;\nexport type StudentProgressFocus = z.infer<typeof studentProgressFocusSchema>;\n\ntype RecommendationType = \"missing\" | \"low_score\" | \"awaiting_feedback\";\n\nfunction calculatePercentage(submission: GradeSubmission) {\n if (submission.gradeReceived == null || !submission.assignment.maxGrade) {\n return null;\n }\n\n return (\n Math.round(\n (submission.gradeReceived / submission.assignment.maxGrade) * 1000,\n ) / 10\n );\n}\n\nfunction calculateTrend(submissions: GradeSubmission[]) {\n const graded = submissions\n .filter(\n (submission) =>\n submission.gradeReceived != null && submission.assignment.maxGrade,\n )\n .sort((a, b) => {\n const aTime = a.submittedAt?.getTime() ?? a.assignment.dueDate.getTime();\n const bTime = b.submittedAt?.getTime() ?? b.assignment.dueDate.getTime();\n return aTime - bTime;\n })\n .map((submission) => calculatePercentage(submission))\n .filter((value): value is number => value != null);\n\n if (graded.length < 2) return 0;\n\n const midpoint = Math.floor(graded.length / 2);\n const first = graded.slice(0, midpoint);\n const second = graded.slice(midpoint);\n const average = (values: number[]) =>\n values.reduce((sum, value) => sum + value, 0) / values.length;\n\n return Math.round((average(second) - average(first)) * 10) / 10;\n}\n\nfunction getOverallGrade(submissions: GradeSubmission[]) {\n let totalWeighted = 0;\n let totalWeight = 0;\n\n for (const submission of submissions) {\n if (\n submission.gradeReceived != null &&\n submission.assignment.maxGrade &&\n submission.assignment.weight\n ) {\n totalWeighted +=\n (submission.gradeReceived / submission.assignment.maxGrade) *\n submission.assignment.weight;\n totalWeight += submission.assignment.weight;\n }\n }\n\n return totalWeight > 0\n ? Math.round((totalWeighted / totalWeight) * 1000) / 10\n : null;\n}\n\nfunction normalizeAssignmentType(\n assignmentType: AssignmentType,\n): AssignmentType {\n if (assignmentType === \"TEST\" || assignmentType === \"QUIZ\") {\n return \"HOMEWORK\";\n }\n\n return assignmentType;\n}\n\nfunction buildSuggestedAssignment(\n recommendationType: RecommendationType,\n submission: GradeSubmission,\n displayName: string,\n percentage: number | null,\n) {\n const sourceTitle = submission.assignment.title;\n const section = submission.assignment.section?.name;\n const titleByType: Record<RecommendationType, string> = {\n missing: `Make-up: ${sourceTitle}`,\n low_score: `Targeted practice: ${sourceTitle}`,\n awaiting_feedback: `Revision follow-up: ${sourceTitle}`,\n };\n const leadByType: Record<RecommendationType, string> = {\n missing: `${displayName} missed the original assignment and needs a focused make-up task on the same material.`,\n low_score:\n percentage != null\n ? `${displayName} scored ${percentage}% and needs targeted reinforcement on the underlying concepts.`\n : `${displayName} needs targeted reinforcement on the underlying concepts.`,\n awaiting_feedback: `${displayName} has work in progress here, so a short follow-up can keep momentum once feedback lands.`,\n };\n\n return {\n title: titleByType[recommendationType],\n instructions: [\n leadByType[recommendationType],\n `Source assignment: ${sourceTitle}.`,\n section ? `Section: ${section}.` : null,\n \"Keep the work concise and focused on the specific area of concern.\",\n ]\n .filter((line): line is string => Boolean(line))\n .join(\"\\n\\n\"),\n type: normalizeAssignmentType(submission.assignment.type),\n maxGrade: submission.assignment.maxGrade ?? 100,\n weight: submission.assignment.weight || 1,\n graded: true,\n sectionId: submission.assignment.section?.id ?? null,\n sourceAssignmentId: submission.assignment.id,\n };\n}\n\nfunction buildRecommendationCandidate(\n submission: GradeSubmission,\n displayName: string,\n now: Date,\n) {\n if (\n submission.recommendationState === \"ASSIGNED\" ||\n submission.recommendationState === \"DISMISSED\"\n ) {\n return null;\n }\n\n const percentage = calculatePercentage(submission);\n const isMissing =\n !submission.submitted &&\n submission.assignment.dueDate.getTime() < now.getTime();\n const isLowScore = percentage != null && percentage < 70;\n const isUnreturned =\n Boolean(submission.submitted) &&\n submission.gradeReceived == null &&\n !submission.returned;\n const reasons: string[] = [];\n let priorityScore = 0;\n let recommendationType: RecommendationType | null = null;\n\n if (isMissing) {\n reasons.push(\"Missing past-due work\");\n priorityScore += 100;\n recommendationType = \"missing\";\n }\n if (isLowScore) {\n reasons.push(`Scored ${percentage}%`);\n priorityScore += 85 - (percentage ?? 0);\n recommendationType ??= \"low_score\";\n }\n if (isUnreturned) {\n reasons.push(\"Awaiting grade or feedback\");\n priorityScore += 15;\n recommendationType ??= \"awaiting_feedback\";\n }\n\n if (priorityScore <= 0 || !recommendationType) {\n return null;\n }\n\n return {\n assignmentId: submission.assignment.id,\n submissionId: submission.id,\n title: submission.assignment.title,\n type: submission.assignment.type,\n sectionId: submission.assignment.section?.id ?? null,\n sectionName: submission.assignment.section?.name ?? null,\n dueDate: submission.assignment.dueDate,\n gradeReceived: submission.gradeReceived,\n maxGrade: submission.assignment.maxGrade,\n percentage,\n submitted: Boolean(submission.submitted),\n returned: Boolean(submission.returned),\n recommendationType,\n reasons,\n priorityScore,\n suggestedAssignment: buildSuggestedAssignment(\n recommendationType,\n submission,\n displayName,\n percentage,\n ),\n };\n}\n\nfunction summarizeStudentRecommendations(\n student: ProgressStudent,\n submissions: GradeSubmission[],\n) {\n const now = new Date();\n const displayName = student.profile?.displayName ?? student.username;\n const recommendationCandidates = submissions\n .map((submission) =>\n buildRecommendationCandidate(submission, displayName, now),\n )\n .filter(\n (\n candidate,\n ): candidate is NonNullable<\n ReturnType<typeof buildRecommendationCandidate>\n > => candidate != null,\n )\n .sort((a, b) => b.priorityScore - a.priorityScore);\n\n const recommendations = recommendationCandidates\n .slice(0, 5)\n .map(({ priorityScore, ...candidate }) => candidate);\n\n const gradedPercentages = submissions\n .map(calculatePercentage)\n .filter((value): value is number => value != null);\n const lowScoreCount = gradedPercentages.filter(\n (percentage) => percentage < 70,\n ).length;\n const missingCount = submissions.filter(\n (submission) =>\n !submission.submitted &&\n submission.assignment.dueDate.getTime() < now.getTime(),\n ).length;\n const trend = calculateTrend(submissions);\n const overallGrade = getOverallGrade(submissions);\n\n const nextSteps = [\n missingCount > 0\n ? `Prioritize ${missingCount} missing assignment${missingCount === 1 ? \"\" : \"s\"}.`\n : null,\n lowScoreCount > 0\n ? `Review ${lowScoreCount} low-scoring assignment${lowScoreCount === 1 ? \"\" : \"s\"} before introducing new material.`\n : null,\n trend < -5\n ? \"Schedule a check-in because recent performance is trending down.\"\n : null,\n recommendationCandidates.length === 0\n ? \"No urgent assignment issues detected from the available grades.\"\n : null,\n ].filter((step): step is string => Boolean(step));\n\n return {\n student: {\n id: student.id,\n username: student.username,\n displayName,\n },\n summary: {\n overallGrade,\n trend,\n completedAssignments: submissions.filter((submission) =>\n Boolean(submission.submitted),\n ).length,\n totalAssignments: submissions.length,\n missingCount,\n lowScoreCount,\n },\n recommendations,\n recommendationCandidates,\n nextSteps,\n priorityScore: recommendationCandidates[0]?.priorityScore ?? 0,\n };\n}\n\nasync function syncRecommendationStates(\n submissions: GradeSubmission[],\n recommendationCandidates: Array<\n NonNullable<ReturnType<typeof buildRecommendationCandidate>>\n >,\n) {\n const candidateSubmissionIds = new Set(\n recommendationCandidates.map((candidate) => candidate.submissionId),\n );\n const updates: Promise<unknown>[] = [];\n\n for (const submission of submissions) {\n const shouldBeOpen = candidateSubmissionIds.has(submission.id);\n\n if (shouldBeOpen && submission.recommendationState === \"NONE\") {\n updates.push(\n prisma.submission.update({\n where: { id: submission.id },\n data: {\n recommendationState: \"OPEN\",\n recommendationUpdatedAt: new Date(),\n },\n }),\n );\n continue;\n }\n\n if (!shouldBeOpen && submission.recommendationState === \"OPEN\") {\n updates.push(\n prisma.submission.update({\n where: { id: submission.id },\n data: {\n recommendationState: \"NONE\",\n recommendationUpdatedAt: new Date(),\n },\n }),\n );\n }\n }\n\n if (updates.length) {\n await Promise.all(updates);\n }\n}\n\nasync function summarizeStudentRecommendationsWithCases(\n student: ProgressStudent,\n submissions: GradeSubmission[],\n) {\n const summary = summarizeStudentRecommendations(student, submissions);\n await syncRecommendationStates(submissions, summary.recommendationCandidates);\n return summary;\n}\n\nasync function loadStudentProgressContext(\n viewerId: string,\n classId: string,\n studentId: string,\n) {\n const isTeacher = await isTeacherInClass(classId, viewerId);\n if (viewerId !== studentId && !isTeacher) {\n throw new TRPCError({\n code: \"UNAUTHORIZED\",\n message: \"You can only view your own progress\",\n });\n }\n\n const [classData, student, submissions] = await Promise.all([\n prisma.class.findUnique({\n where: { id: classId },\n select: { id: true, name: true, subject: true },\n }),\n prisma.user.findFirst({\n where: {\n id: studentId,\n studentIn: { some: { id: classId } },\n },\n select: {\n id: true,\n username: true,\n profile: { select: { displayName: true } },\n },\n }),\n prisma.submission.findMany({\n where: {\n studentId,\n assignment: { classId, graded: true },\n },\n include: {\n assignment: {\n select: {\n id: true,\n title: true,\n dueDate: true,\n maxGrade: true,\n weight: true,\n type: true,\n section: { select: { id: true, name: true } },\n },\n },\n },\n orderBy: { assignment: { dueDate: \"asc\" } },\n }),\n ]);\n\n if (!classData) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Class not found\" });\n }\n\n if (!student) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Student not found in this class\",\n });\n }\n\n return { classData, student, submissions };\n}\n\nexport async function getStudentProgressRecommendations(\n viewerId: string,\n classId: string,\n studentId: string,\n) {\n const { student, submissions } = await loadStudentProgressContext(\n viewerId,\n classId,\n studentId,\n );\n\n const summary = await summarizeStudentRecommendationsWithCases(\n student,\n submissions,\n );\n const {\n recommendationCandidates: _recommendationCandidates,\n ...summaryResponse\n } = summary;\n return {\n student: summaryResponse.student,\n summary: summaryResponse.summary,\n recommendations: summaryResponse.recommendations,\n nextSteps: summaryResponse.nextSteps,\n };\n}\n\nexport async function getClassProgressRecommendations(\n viewerId: string,\n classId: string,\n) {\n const teacherInClass = await isTeacherInClass(classId, viewerId);\n if (!teacherInClass) {\n throw new TRPCError({\n code: \"UNAUTHORIZED\",\n message: \"Only teachers can view class-wide recommendations\",\n });\n }\n\n const classData = await prisma.class.findUnique({\n where: { id: classId },\n select: {\n id: true,\n name: true,\n subject: true,\n students: {\n select: {\n id: true,\n username: true,\n profile: { select: { displayName: true } },\n submissions: {\n where: {\n assignment: { classId, graded: true },\n },\n include: {\n assignment: {\n select: {\n id: true,\n title: true,\n dueDate: true,\n maxGrade: true,\n weight: true,\n type: true,\n section: { select: { id: true, name: true } },\n },\n },\n },\n orderBy: { assignment: { dueDate: \"asc\" } },\n },\n },\n },\n },\n });\n\n if (!classData) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Class not found\" });\n }\n\n const students = (\n await Promise.all(\n classData.students.map((student) =>\n summarizeStudentRecommendationsWithCases(student, student.submissions),\n ),\n )\n )\n .map(\n ({ recommendationCandidates: _recommendationCandidates, ...student }) =>\n student,\n )\n .filter((student) => student.recommendations.length > 0)\n .sort((a, b) => b.priorityScore - a.priorityScore);\n\n const totalRecommendations = students.reduce(\n (sum, student) => sum + student.recommendations.length,\n 0,\n );\n const studentsWithConcerns = students.length;\n const totalMissingAssignments = students.reduce(\n (sum, student) => sum + student.summary.missingCount,\n 0,\n );\n const totalLowScoreAssignments = students.reduce(\n (sum, student) => sum + student.summary.lowScoreCount,\n 0,\n );\n\n return {\n class: {\n id: classData.id,\n name: classData.name,\n subject: classData.subject,\n },\n summary: {\n studentsWithConcerns,\n totalRecommendations,\n totalMissingAssignments,\n totalLowScoreAssignments,\n },\n students: students.map(({ priorityScore, ...student }) => student),\n };\n}\n\nexport async function dismissStudentRecommendation(\n viewerId: string,\n input: {\n classId: string;\n studentId: string;\n submissionId: string;\n },\n) {\n const teacherInClass = await isTeacherInClass(input.classId, viewerId);\n if (!teacherInClass) {\n throw new TRPCError({\n code: \"UNAUTHORIZED\",\n message: \"Only teachers can dismiss recommendations\",\n });\n }\n\n const submission = await prisma.submission.findFirst({\n where: {\n id: input.submissionId,\n studentId: input.studentId,\n assignment: {\n classId: input.classId,\n },\n },\n select: { id: true },\n });\n\n if (!submission) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Submission not found for this student in this class\",\n });\n }\n\n await prisma.submission.update({\n where: { id: input.submissionId },\n data: {\n recommendationState: \"DISMISSED\",\n recommendationUpdatedAt: new Date(),\n },\n });\n\n return { success: true };\n}\n\nfunction buildProgressSummary(submissions: GradeSubmission[]) {\n return submissions.map((submission) => ({\n title: submission.assignment.title,\n type: submission.assignment.type,\n dueDate: submission.assignment.dueDate.toISOString(),\n submitted: Boolean(submission.submitted),\n returned: Boolean(submission.returned),\n gradeReceived: submission.gradeReceived,\n maxGrade: submission.assignment.maxGrade,\n percentage: calculatePercentage(submission),\n section: submission.assignment.section?.name ?? null,\n }));\n}\n\nfunction buildStructuredProgressContext(\n classData: { id: string; name: string; subject: string | null },\n student: ProgressStudent,\n submissions: GradeSubmission[],\n) {\n const displayName = student.profile?.displayName ?? student.username;\n const recommendationSummary = summarizeStudentRecommendations(\n student,\n submissions,\n );\n\n return {\n class: classData,\n student: { id: student.id, username: student.username, displayName },\n progress: {\n overallGrade: getOverallGrade(submissions),\n trend: calculateTrend(submissions),\n summary: recommendationSummary.summary,\n assignments: buildProgressSummary(submissions),\n recommendations: recommendationSummary.recommendations.map(\n (recommendation) => ({\n title: recommendation.title,\n recommendationType: recommendation.recommendationType,\n percentage: recommendation.percentage,\n submitted: recommendation.submitted,\n reasons: recommendation.reasons,\n sectionName: recommendation.sectionName,\n }),\n ),\n },\n };\n}\n\nfunction buildStructuredInferenceMessages(\n instruction: string,\n context: ReturnType<typeof buildStructuredProgressContext>,\n) {\n return [\n {\n role: \"system\" as const,\n content:\n \"You are an educational progress analyst. Use only the provided class and grade context. Be specific, concise, supportive, and do not fabricate assignments, grades, or sections.\",\n },\n {\n role: \"user\" as const,\n content: JSON.stringify(context),\n },\n {\n role: \"user\" as const,\n content: instruction,\n },\n ];\n}\n\ntype ProgressInferenceCacheRow<T> = {\n epoch: number;\n cachedAt: number;\n value: T;\n};\n\nconst studentProgressInsightsCache = new Map<\n string,\n ProgressInferenceCacheRow<StudentProgressInsights>\n>();\nconst studentProgressForecastCache = new Map<\n string,\n ProgressInferenceCacheRow<StudentProgressForecast>\n>();\nconst studentProgressFocusCache = new Map<\n string,\n ProgressInferenceCacheRow<StudentProgressFocus>\n>();\n\nexport async function getStudentProgressInsights(\n viewerId: string,\n classId: string,\n studentId: string,\n) {\n const cached = getStudentProgressInferenceCacheHit(\n studentProgressInsightsCache,\n classId,\n studentId,\n );\n\n const { classData, student, submissions } = await loadStudentProgressContext(\n viewerId,\n classId,\n studentId,\n );\n\n if (cached !== undefined) {\n return cached;\n }\n\n const context = buildStructuredProgressContext(\n classData,\n student,\n submissions,\n );\n\n const value = await inference<StudentProgressInsights>(\n buildStructuredInferenceMessages(\n [\n \"Generate an AI analytics summary for this one student's dashboard.\",\n \"The summary must be 2-4 plain-prose sentences: lead with overall grade and trajectory, then call out one strength and one concern with assignment titles.\",\n \"Strengths and growth items need 2-6 word labels and one-line evidence grounded in assignment titles or sections.\",\n \"Talking points must be 3-5 specific teacher check-in prompts with no numbering.\",\n ].join(\" \"),\n context,\n ),\n studentProgressInsightsSchema,\n {\n reason: \"student_progress_insights\",\n metadata: { viewerId, classId, studentId },\n },\n );\n setStudentProgressInferenceCache(\n studentProgressInsightsCache,\n classId,\n studentId,\n value,\n );\n return value;\n}\n\nexport async function getStudentProgressForecast(\n viewerId: string,\n classId: string,\n studentId: string,\n) {\n const cached = getStudentProgressInferenceCacheHit(\n studentProgressForecastCache,\n classId,\n studentId,\n );\n\n const { classData, student, submissions } = await loadStudentProgressContext(\n viewerId,\n classId,\n studentId,\n );\n\n if (cached !== undefined) {\n return cached;\n }\n\n const context = buildStructuredProgressContext(\n classData,\n student,\n submissions,\n );\n\n const value = await inference<StudentProgressForecast>(\n buildStructuredInferenceMessages(\n \"Predict the student's next graded assignment percentage band and risk status. Base the forecast on trajectory, missing work, low scores, and assignment recency. Keep the reason to one sentence.\",\n context,\n ),\n studentProgressForecastSchema,\n {\n reason: \"student_progress_forecast\",\n metadata: { viewerId, classId, studentId },\n },\n );\n setStudentProgressInferenceCache(\n studentProgressForecastCache,\n classId,\n studentId,\n value,\n );\n return value;\n}\n\nexport async function getStudentProgressFocus(\n viewerId: string,\n classId: string,\n studentId: string,\n) {\n const cached = getStudentProgressInferenceCacheHit(\n studentProgressFocusCache,\n classId,\n studentId,\n );\n\n const { classData, student, submissions } = await loadStudentProgressContext(\n viewerId,\n classId,\n studentId,\n );\n\n if (cached !== undefined) {\n return cached;\n }\n \n const context = buildStructuredProgressContext(\n classData,\n student,\n submissions,\n );\n\n const value = await inference<StudentProgressFocus>(\n buildStructuredInferenceMessages(\n \"Generate 3-5 student-facing weekly focus items. Each item should be specific, actionable, and grounded in the student's grade pattern or assignment titles. No numbering in the strings.\",\n context,\n ),\n studentProgressFocusSchema,\n {\n reason: \"student_progress_focus\",\n metadata: { viewerId, classId, studentId },\n },\n );\n setStudentProgressInferenceCache(\n studentProgressFocusCache,\n classId,\n studentId,\n value,\n );\n return value;\n}\n\nexport async function chatAboutStudentProgress(\n viewerId: string,\n input: {\n classId: string;\n studentId: string;\n message: string;\n history?: ProgressChatMessage[];\n },\n) {\n const { classData, student, submissions } = await loadStudentProgressContext(\n viewerId,\n input.classId,\n input.studentId,\n );\n\n const displayName = student.profile?.displayName ?? student.username;\n const summary = {\n overallGrade: getOverallGrade(submissions),\n trend: calculateTrend(submissions),\n assignments: buildProgressSummary(submissions),\n };\n\n const messages = [\n {\n role: \"system\" as const,\n content:\n \"You are an educational progress assistant for teachers and students. Use only the provided class and grade context. Be concise, specific, supportive, and avoid fabricating grades or assignments.\",\n },\n {\n role: \"user\" as const,\n content: JSON.stringify({\n class: classData,\n student: { id: student.id, username: student.username, displayName },\n progress: summary,\n }),\n },\n ...(input.history ?? []).slice(-8).map((message) => ({\n role: message.role,\n content: message.content,\n })),\n { role: \"user\" as const, content: input.message },\n ];\n\n try {\n const response = await inference<string>(messages, undefined, {\n reason: \"student_progress_chat\",\n metadata: {\n viewerId,\n classId: input.classId,\n studentId: input.studentId,\n },\n });\n if (typeof response !== \"string\" || response.trim().length === 0) {\n throw new Error(\"Student progress chat returned an empty response\");\n }\n return { message: response, isFallback: false };\n } catch (error) {\n logger.error(\"Failed to generate student progress chat response\", {\n error,\n classId: input.classId,\n studentId: input.studentId,\n });\n\n const overall =\n summary.overallGrade == null\n ? \"not enough graded work\"\n : `${summary.overallGrade}%`;\n const missingItems = summary.assignments.filter(\n (assignment) =>\n !assignment.submitted &&\n new Date(assignment.dueDate).getTime() < Date.now(),\n ).length;\n const lowScores = summary.assignments.filter(\n (assignment) =>\n assignment.percentage != null && assignment.percentage < 70,\n ).length;\n const awaitingFeedback = summary.assignments.filter(\n (assignment) =>\n assignment.submitted &&\n assignment.gradeReceived == null &&\n !assignment.returned,\n ).length;\n\n const fallbackParts = [\n `${displayName}'s current overall grade is ${overall}.`,\n missingItems > 0\n ? `${missingItems} assignment${missingItems === 1 ? \"\" : \"s\"} ${missingItems === 1 ? \"appears\" : \"appear\"} overdue.`\n : null,\n lowScores > 0\n ? `${lowScores} graded assignment${lowScores === 1 ? \" is\" : \"s are\"} below 70%.`\n : null,\n awaitingFeedback > 0\n ? `${awaitingFeedback} assignment${awaitingFeedback === 1 ? \" is\" : \"s are\"} still awaiting feedback.`\n : null,\n summary.trend < -5\n ? \"Recent performance is trending down, so a check-in would be reasonable.\"\n : \"Performance looks relatively steady from the available data.\",\n ].filter((part): part is string => Boolean(part));\n\n return {\n message: fallbackParts.join(\" \"),\n isFallback: true,\n };\n }\n}\n"],"names":[],"mappings":"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","debug_id":"159c0157-8d54-553a-84a9-e84c0b152f43"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * In-process cache for student progress AI panels (insights / forecast / focus).
3
+ * Epoch bumps invalidate all three for a given class + student (e.g. after a grade update).
4
+ * Entries also expire after one week so stale AI summaries eventually refresh.
5
+ */
6
+ /** Call when a teacher updates `gradeReceived` for that student's work in this class. */
7
+ export declare function invalidateStudentProgressInferenceCache(classId: string, studentId: string): void;
8
+ type CacheRow<T> = {
9
+ epoch: number;
10
+ cachedAt: number;
11
+ value: T;
12
+ };
13
+ export declare function getStudentProgressInferenceCacheHit<T>(store: Map<string, CacheRow<T>>, classId: string, studentId: string): T | undefined;
14
+ export declare function setStudentProgressInferenceCache<T>(store: Map<string, CacheRow<T>>, classId: string, studentId: string, value: T): void;
15
+ export {};
16
+ //# sourceMappingURL=studentProgressInferenceCache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"studentProgressInferenceCache.d.ts","sourceRoot":"/","sources":["services/studentProgressInferenceCache.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAiBH,yFAAyF;AACzF,wBAAgB,uCAAuC,CACrD,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,QAIlB;AAED,KAAK,QAAQ,CAAC,CAAC,IAAI;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,CAAC,CAAA;CAAE,CAAC;AAEjE,wBAAgB,mCAAmC,CAAC,CAAC,EACnD,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,EAC/B,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,GAChB,CAAC,GAAG,SAAS,CAcf;AAED,wBAAgB,gCAAgC,CAAC,CAAC,EAChD,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,EAC/B,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,CAAC,QAOT"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * In-process cache for student progress AI panels (insights / forecast / focus).
3
+ * Epoch bumps invalidate all three for a given class + student (e.g. after a grade update).
4
+ * Entries also expire after one week so stale AI summaries eventually refresh.
5
+ */
6
+
7
+ !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]="44d7e2e7-05ee-5dcf-90bd-a38a00aaf487")}catch(e){}}();
8
+ const WEEK_MS = 7 * 24 * 60 * 60 * 1000;
9
+ const epochByScope = new Map();
10
+ function scopeKey(classId, studentId) {
11
+ return `${classId}:${studentId}`;
12
+ }
13
+ function currentStudentProgressInferenceEpoch(classId, studentId) {
14
+ return epochByScope.get(scopeKey(classId, studentId)) ?? 0;
15
+ }
16
+ /** Call when a teacher updates `gradeReceived` for that student's work in this class. */
17
+ export function invalidateStudentProgressInferenceCache(classId, studentId) {
18
+ const key = scopeKey(classId, studentId);
19
+ epochByScope.set(key, (epochByScope.get(key) ?? 0) + 1);
20
+ }
21
+ export function getStudentProgressInferenceCacheHit(store, classId, studentId) {
22
+ const key = scopeKey(classId, studentId);
23
+ const epoch = currentStudentProgressInferenceEpoch(classId, studentId);
24
+ const hit = store.get(key);
25
+ if (!hit) {
26
+ return undefined;
27
+ }
28
+ const fresh = hit.epoch === epoch && Date.now() - hit.cachedAt < WEEK_MS;
29
+ if (!fresh) {
30
+ store.delete(key);
31
+ return undefined;
32
+ }
33
+ return hit.value;
34
+ }
35
+ export function setStudentProgressInferenceCache(store, classId, studentId, value) {
36
+ store.set(scopeKey(classId, studentId), {
37
+ epoch: currentStudentProgressInferenceEpoch(classId, studentId),
38
+ cachedAt: Date.now(),
39
+ value,
40
+ });
41
+ }
42
+ //# sourceMappingURL=studentProgressInferenceCache.js.map
43
+ //# debugId=44d7e2e7-05ee-5dcf-90bd-a38a00aaf487
@@ -0,0 +1 @@
1
+ {"version":3,"file":"studentProgressInferenceCache.js","sources":["services/studentProgressInferenceCache.ts"],"sourceRoot":"/","sourcesContent":["/**\n * In-process cache for student progress AI panels (insights / forecast / focus).\n * Epoch bumps invalidate all three for a given class + student (e.g. after a grade update).\n * Entries also expire after one week so stale AI summaries eventually refresh.\n */\n\nconst WEEK_MS = 7 * 24 * 60 * 60 * 1000;\n\nconst epochByScope = new Map<string, number>();\n\nfunction scopeKey(classId: string, studentId: string) {\n return `${classId}:${studentId}`;\n}\n\nfunction currentStudentProgressInferenceEpoch(\n classId: string,\n studentId: string,\n) {\n return epochByScope.get(scopeKey(classId, studentId)) ?? 0;\n}\n\n/** Call when a teacher updates `gradeReceived` for that student's work in this class. */\nexport function invalidateStudentProgressInferenceCache(\n classId: string,\n studentId: string,\n) {\n const key = scopeKey(classId, studentId);\n epochByScope.set(key, (epochByScope.get(key) ?? 0) + 1);\n}\n\ntype CacheRow<T> = { epoch: number; cachedAt: number; value: T };\n\nexport function getStudentProgressInferenceCacheHit<T>(\n store: Map<string, CacheRow<T>>,\n classId: string,\n studentId: string,\n): T | undefined {\n const key = scopeKey(classId, studentId);\n const epoch = currentStudentProgressInferenceEpoch(classId, studentId);\n const hit = store.get(key);\n if (!hit) {\n return undefined;\n }\n const fresh =\n hit.epoch === epoch && Date.now() - hit.cachedAt < WEEK_MS;\n if (!fresh) {\n store.delete(key);\n return undefined;\n }\n return hit.value;\n}\n\nexport function setStudentProgressInferenceCache<T>(\n store: Map<string, CacheRow<T>>,\n classId: string,\n studentId: string,\n value: T,\n) {\n store.set(scopeKey(classId, studentId), {\n epoch: currentStudentProgressInferenceEpoch(classId, studentId),\n cachedAt: Date.now(),\n value,\n });\n}\n"],"names":[],"mappings":"AAAA;;;;GAIG;;;AAEH,MAAM,OAAO,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AAExC,MAAM,YAAY,GAAG,IAAI,GAAG,EAAkB,CAAC;AAE/C,SAAS,QAAQ,CAAC,OAAe,EAAE,SAAiB;IAClD,OAAO,GAAG,OAAO,IAAI,SAAS,EAAE,CAAC;AACnC,CAAC;AAED,SAAS,oCAAoC,CAC3C,OAAe,EACf,SAAiB;IAEjB,OAAO,YAAY,CAAC,GAAG,CAAC,QAAQ,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC,IAAI,CAAC,CAAC;AAC7D,CAAC;AAED,yFAAyF;AACzF,MAAM,UAAU,uCAAuC,CACrD,OAAe,EACf,SAAiB;IAEjB,MAAM,GAAG,GAAG,QAAQ,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;IACzC,YAAY,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;AAC1D,CAAC;AAID,MAAM,UAAU,mCAAmC,CACjD,KAA+B,EAC/B,OAAe,EACf,SAAiB;IAEjB,MAAM,GAAG,GAAG,QAAQ,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;IACzC,MAAM,KAAK,GAAG,oCAAoC,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;IACvE,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAC3B,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,MAAM,KAAK,GACT,GAAG,CAAC,KAAK,KAAK,KAAK,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,GAAG,CAAC,QAAQ,GAAG,OAAO,CAAC;IAC7D,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAClB,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,OAAO,GAAG,CAAC,KAAK,CAAC;AACnB,CAAC;AAED,MAAM,UAAU,gCAAgC,CAC9C,KAA+B,EAC/B,OAAe,EACf,SAAiB,EACjB,KAAQ;IAER,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,OAAO,EAAE,SAAS,CAAC,EAAE;QACtC,KAAK,EAAE,oCAAoC,CAAC,OAAO,EAAE,SAAS,CAAC;QAC/D,QAAQ,EAAE,IAAI,CAAC,GAAG,EAAE;QACpB,KAAK;KACN,CAAC,CAAC;AACL,CAAC","debug_id":"44d7e2e7-05ee-5dcf-90bd-a38a00aaf487"}
@@ -10,6 +10,9 @@ export declare function getWorksheet(worksheetId: string, userId: string): Promi
10
10
  section: string;
11
11
  syllabus: string | null;
12
12
  departmentId: string | null;
13
+ discussionsEnabled: boolean;
14
+ studentsCanCreateDiscussions: boolean;
15
+ studentsCanReply: boolean;
13
16
  } | null;
14
17
  questions: {
15
18
  type: import(".prisma/client").$Enums.WorksheetQuestionType;
@@ -1 +1 @@
1
- {"version":3,"file":"worksheet.d.ts","sourceRoot":"/","sources":["services/worksheet.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"worksheet.d.ts","sourceRoot":"/","sources":["services/worksheet.ts"],"names":[],"mappings":"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"}
@@ -3,7 +3,7 @@
3
3
  * Integrates with gradeWorksheet pipeline for auto-grading.
4
4
  */
5
5
 
6
- !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]="d5088046-353b-5b26-b9c3-10f78d48d45e")}catch(e){}}();
6
+ !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]="dbf4168b-2ccd-58fc-80da-0cb74d76cf44")}catch(e){}}();
7
7
  import { TRPCError } from "@trpc/server";
8
8
  import { JobStatus } from "@prisma/client";
9
9
  import { prisma } from "../lib/prisma.js";
@@ -11,7 +11,7 @@ import { getFileUrl } from "../lib/fileUpload.js";
11
11
  import { cancelGradePipeline, regradeWorksheetPipeline } from "../pipelines/gradeWorksheet.js";
12
12
  import { enqueueRunJob } from "../workers/queues.js";
13
13
  import { logger } from "../utils/logger.js";
14
- import { findWorksheetByIdMinimal, findWorksheetByIdForClassMember, findWorksheetByIdForTeacher, findWorksheetsByClassId, createWorksheet, duplicateWorksheet, updateWorksheet, deleteWorksheet, findQuestionsByWorksheetId, createWorksheetQuestion, updateWorksheetQuestion, deleteWorksheetQuestion, findSubmissionByIdForWorksheetAccess, findOrCreateWorksheetResponse, findWorksheetResponseByIdForTeacher, findWorksheetResponseWithResponsesForStudent, findWorksheetQuestionByIdForTeacher, findWorksheetQuestionByIdInWorksheet, updateStudentQuestionProgress, createStudentQuestionProgress, findStudentQuestionProgress, updateStudentQuestionProgressForGrading, createStudentQuestionProgressForGrading, createComment, findStudentQuestionProgressByIdForTeacher, findStudentQuestionProgressByIdForTeacherAndResponse, } from "../models/worksheet.js";
14
+ import { findWorksheetByIdMinimal, findWorksheetByIdForClassMember, findWorksheetByIdForTeacher, findWorksheetsByClassId, createWorksheet, duplicateWorksheet, updateWorksheet, deleteWorksheet, findQuestionsByWorksheetId, createWorksheetQuestion, updateWorksheetQuestion, deleteWorksheetQuestion, findSubmissionByIdForWorksheetAccess, findOrCreateWorksheetResponse, findWorksheetResponseByIdForTeacher, findWorksheetResponseWithResponsesForStudent, findWorksheetQuestionByIdForTeacher, findWorksheetQuestionByIdInWorksheet, updateStudentQuestionProgress, createStudentQuestionProgress, findStudentQuestionProgress, updateStudentQuestionProgressForGrading, createStudentQuestionProgressForGrading, createComment, findStudentQuestionProgressByIdForTeacher, findStudentQuestionProgressByIdForStudentOrTeacher, findStudentQuestionProgressByIdForTeacherAndResponse, } from "../models/worksheet.js";
15
15
  const sanitizeQuestionOptionsForStudent = (options) => {
16
16
  if (!Array.isArray(options))
17
17
  return options;
@@ -428,11 +428,11 @@ export async function gradeAnswer(questionId, studentWorksheetResponseId, userId
428
428
  });
429
429
  }
430
430
  export async function addCommentToResponse(responseId, comment, authorId) {
431
- const progress = await findStudentQuestionProgressByIdForTeacher(responseId, authorId);
431
+ const progress = await findStudentQuestionProgressByIdForStudentOrTeacher(responseId, authorId);
432
432
  if (!progress) {
433
433
  throw new TRPCError({
434
434
  code: "FORBIDDEN",
435
- message: "Only class teachers can comment on worksheet responses",
435
+ message: "You can only comment on your own worksheet answers or on answers for classes you teach",
436
436
  });
437
437
  }
438
438
  return createComment({
@@ -442,4 +442,4 @@ export async function addCommentToResponse(responseId, comment, authorId) {
442
442
  });
443
443
  }
444
444
  //# sourceMappingURL=worksheet.js.map
445
- //# debugId=d5088046-353b-5b26-b9c3-10f78d48d45e
445
+ //# debugId=dbf4168b-2ccd-58fc-80da-0cb74d76cf44
@@ -1 +1 @@
1
- {"version":3,"file":"worksheet.js","sources":["services/worksheet.ts"],"sourceRoot":"/","sourcesContent":["/**\n * Worksheet service – CRUD for worksheets, questions, submissions, and AI grading.\n * Integrates with gradeWorksheet pipeline for auto-grading.\n */\nimport { TRPCError } from \"@trpc/server\";\nimport { JobStatus, type WorksheetQuestionType } from \"@prisma/client\";\nimport { prisma } from \"../lib/prisma.js\";\nimport { getFileUrl } from \"../lib/fileUpload.js\";\nimport { cancelGradePipeline, regradeWorksheetPipeline } from \"../pipelines/gradeWorksheet.js\";\nimport { enqueueRunJob } from \"../workers/queues.js\";\nimport { logger } from \"../utils/logger.js\";\nimport {\n findWorksheetByIdMinimal,\n findWorksheetByIdForClassMember,\n findWorksheetByIdForTeacher,\n findWorksheetsByClassId,\n createWorksheet,\n duplicateWorksheet,\n updateWorksheet,\n deleteWorksheet,\n findQuestionsByWorksheetId,\n createWorksheetQuestion,\n updateWorksheetQuestion,\n deleteWorksheetQuestion,\n findSubmissionByIdForWorksheetAccess,\n findOrCreateWorksheetResponse,\n findWorksheetResponseByIdForTeacher,\n findWorksheetResponseWithResponsesForStudent,\n findWorksheetQuestionByIdForTeacher,\n findWorksheetQuestionByIdInWorksheet,\n updateStudentQuestionProgress,\n createStudentQuestionProgress,\n findStudentQuestionProgress,\n updateStudentQuestionProgressForGrading,\n createStudentQuestionProgressForGrading,\n createComment,\n findStudentQuestionProgressByIdForTeacher,\n findStudentQuestionProgressByIdForTeacherAndResponse,\n} from \"../models/worksheet.js\";\n\ntype MCQOptions = { id: string; text: string; isCorrect: boolean }[];\n\nconst sanitizeQuestionOptionsForStudent = (options: unknown) => {\n if (!Array.isArray(options)) return options;\n\n return options.map((option) => {\n if (!option || typeof option !== \"object\") return option;\n\n const { isCorrect: _isCorrect, ...rest } = option as Record<string, unknown>;\n return rest;\n });\n};\n\nconst sanitizeWorksheetForStudent = <T extends {\n questions: Array<Record<string, unknown>>;\n}>(worksheet: T) => ({\n ...worksheet,\n questions: worksheet.questions.map((question) => ({\n ...question,\n answer: \"\",\n markScheme: null,\n options: sanitizeQuestionOptionsForStudent(question.options),\n })),\n});\n\n/** Get worksheet with questions and mark schemes. */\nexport async function getWorksheet(worksheetId: string, userId: string) {\n const teacherWorksheet = await findWorksheetByIdForTeacher(worksheetId, userId);\n if (teacherWorksheet) {\n return teacherWorksheet;\n }\n\n const studentWorksheet = await findWorksheetByIdForClassMember(worksheetId, userId);\n if (!studentWorksheet) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"You do not have access to this worksheet\",\n });\n }\n\n return sanitizeWorksheetForStudent(studentWorksheet);\n}\n\n/** Check if worksheet exists. */\nexport async function worksheetExists(id: string) {\n const worksheet = await findWorksheetByIdMinimal(id);\n return !!worksheet;\n}\n\nexport async function listWorksheets(classId: string) {\n const worksheets = await findWorksheetsByClassId(classId);\n return worksheets.map((w) => ({\n ...w,\n questionCount: w.questions.length,\n }));\n}\n\nexport async function updateWorksheetRecord(\n worksheetId: string,\n userId: string,\n data: { name?: string }\n) {\n const worksheet = await findWorksheetByIdForTeacher(worksheetId, userId);\n if (!worksheet) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Only class teachers can update worksheets\",\n });\n }\n return updateWorksheet(worksheetId, data);\n}\n\nexport async function deleteWorksheetRecord(worksheetId: string, userId: string) {\n const worksheet = await findWorksheetByIdForTeacher(worksheetId, userId);\n if (!worksheet) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Only class teachers can delete worksheets\",\n });\n }\n return deleteWorksheet(worksheetId);\n}\n\nexport async function createWorksheetRecord(classId: string, name: string) {\n return createWorksheet({ classId, name });\n}\n\n/**\n * Duplicate a worksheet into another class. Caller must teach both the source worksheet's class\n * and the target class.\n */\nexport async function duplicateWorksheetRecord(\n worksheetId: string,\n classId: string,\n name: string,\n userId: string\n) {\n const source = await findWorksheetByIdForTeacher(worksheetId, userId);\n if (!source) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message:\n \"Worksheet not found or you are not a teacher of the class it belongs to\",\n });\n }\n\n const targetOk = await prisma.class.findFirst({\n where: {\n id: classId,\n teachers: { some: { id: userId } },\n },\n select: { id: true },\n });\n\n if (!targetOk) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"You are not a teacher of the target class\",\n });\n }\n\n const created = await duplicateWorksheet(worksheetId, classId, name);\n if (!created) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Worksheet not found\",\n });\n }\n\n return created;\n}\n\n/**\n * Kick off PDF-to-worksheet conversion asynchronously.\n *\n * We synchronously create a placeholder Worksheet row (status PENDING) so the\n * caller has an id to navigate to immediately, then enqueue a BullMQ job that\n * fills in the real title and questions. Progress is broadcast on the\n * per-worksheet job Pusher channel; clients should subscribe there to drive\n * loading UI.\n */\nexport async function createWorksheetFromPdf(\n classId: string,\n url: string,\n userId?: string,\n options: { name?: string } = {},\n) {\n const placeholder = await prisma.worksheet.create({\n data: {\n classId,\n name: options.name?.trim() || \"Converting PDF…\",\n status: JobStatus.PENDING,\n },\n });\n\n try {\n const { runJobId } = await enqueueRunJob(\n \"PDF_TO_WORKSHEET\",\n { gcsUrl: url, classId, worksheetId: placeholder.id },\n { classId, userId: userId ?? null },\n );\n\n await prisma.worksheet\n .update({\n where: { id: placeholder.id },\n data: { runJobId },\n })\n .catch((linkError) => {\n logger.error(\n \"createWorksheetFromPdf: failed to link RunJob to Worksheet\",\n {\n worksheetId: placeholder.id,\n runJobId,\n error:\n linkError instanceof Error\n ? linkError.message\n : String(linkError),\n },\n );\n });\n\n return {\n ...placeholder,\n runJobId,\n };\n } catch (error) {\n await prisma.worksheet\n .update({\n where: { id: placeholder.id },\n data: { status: JobStatus.FAILED },\n })\n .catch(() => undefined);\n\n logger.error(\"createWorksheetFromPdf: failed to enqueue conversion\", {\n worksheetId: placeholder.id,\n classId,\n error: error instanceof Error ? error.message : String(error),\n });\n\n throw new TRPCError({\n code: \"INTERNAL_SERVER_ERROR\",\n message: \"Failed to start worksheet conversion\",\n });\n }\n}\n\n/**\n * Create a worksheet from a PDF that was uploaded through the direct-upload flow.\n */\nexport async function createWorksheetFromUploadedPdf(\n classId: string,\n fileId: string,\n userId: string,\n options: { name?: string } = {},\n) {\n const file = await prisma.file.findFirst({\n where: {\n id: fileId,\n userId,\n uploadStatus: \"COMPLETED\",\n },\n select: {\n id: true,\n path: true,\n type: true,\n name: true,\n },\n });\n\n if (!file) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Uploaded file not found or not ready\",\n });\n }\n\n const isPdf =\n file.type === \"application/pdf\" || file.path.toLowerCase().endsWith(\".pdf\");\n if (!isPdf) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Only PDF files can be converted into worksheets\",\n });\n }\n\n const signedUrl = await getFileUrl(file.path);\n return createWorksheetFromPdf(classId, signedUrl, userId, {\n name: options.name ?? file.name ?? undefined,\n });\n}\n\nexport async function addQuestionToWorksheet(\n worksheetId: string,\n userId: string,\n data: {\n question: string;\n answer: string;\n points?: number;\n options?: object;\n markScheme?: object;\n type: WorksheetQuestionType;\n }\n) {\n const worksheet = await findWorksheetByIdForTeacher(worksheetId, userId);\n if (!worksheet) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Only class teachers can modify worksheet questions\",\n });\n }\n return createWorksheetQuestion({\n worksheetId,\n ...data,\n });\n}\n\nexport async function reorderWorksheetQuestions(\n worksheetId: string,\n userId: string,\n movedId: string,\n position: \"before\" | \"after\",\n targetId: string\n) {\n const worksheet = await findWorksheetByIdForTeacher(worksheetId, userId);\n if (!worksheet) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Only class teachers can reorder worksheet questions\",\n });\n }\n\n const questions = await findQuestionsByWorksheetId(worksheetId);\n const movedIdx = questions.findIndex((q) => q.id === movedId);\n if (movedIdx === -1) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Moved question not found\",\n });\n }\n const targetIdx = questions.findIndex((q) => q.id === targetId);\n if (targetIdx === -1) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Target question not found\",\n });\n }\n if (movedId === targetId) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Moved question and target question must be different\",\n });\n }\n\n const withoutMoved = questions.filter((q) => q.id !== movedId);\n const targetIdxWithoutMoved = withoutMoved.findIndex((q) => q.id === targetId);\n const insertIdx =\n position === \"before\" ? targetIdxWithoutMoved : targetIdxWithoutMoved + 1;\n const next = [\n ...withoutMoved.slice(0, insertIdx).map((item) => ({ id: item.id })),\n { id: movedId },\n ...withoutMoved.slice(insertIdx).map((item) => ({ id: item.id })),\n ];\n\n await prisma.$transaction(\n next.map((item, index) =>\n prisma.worksheetQuestion.update({\n where: { id: item.id },\n data: { order: index },\n })\n )\n );\n return next;\n}\n\nexport async function updateWorksheetQuestionRecord(\n worksheetId: string,\n questionId: string,\n userId: string,\n data: {\n question?: string;\n answer?: string;\n points?: number;\n options?: object;\n markScheme?: object;\n type?: WorksheetQuestionType;\n }\n) {\n const question = await findWorksheetQuestionByIdForTeacher(questionId, userId);\n if (!question || question.worksheetId !== worksheetId) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Only class teachers can update worksheet questions\",\n });\n }\n return updateWorksheetQuestion(questionId, data);\n}\n\nexport async function deleteWorksheetQuestionRecord(\n worksheetId: string,\n questionId: string,\n userId: string\n) {\n const question = await findWorksheetQuestionByIdForTeacher(questionId, userId);\n if (!question || question.worksheetId !== worksheetId) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Only class teachers can delete worksheet questions\",\n });\n }\n return deleteWorksheetQuestion(questionId);\n}\n\nexport async function getWorksheetSubmission(\n worksheetId: string,\n submissionId: string,\n userId: string\n) {\n const worksheet = await findWorksheetByIdForClassMember(worksheetId, userId);\n if (!worksheet) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"You do not have access to this worksheet\",\n });\n }\n\n const submission = await findSubmissionByIdForWorksheetAccess(\n submissionId,\n worksheetId,\n worksheet.classId,\n userId\n );\n if (!submission) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"You do not have access to this worksheet submission\",\n });\n }\n return findOrCreateWorksheetResponse(\n submissionId,\n worksheetId,\n submission.studentId\n );\n}\n\nexport async function answerWorksheetQuestion(\n worksheetResponseId: string,\n questionId: string,\n response: string,\n userId: string\n) {\n const worksheetResponse = await findWorksheetResponseWithResponsesForStudent(\n worksheetResponseId,\n userId,\n questionId\n );\n if (!worksheetResponse) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"You can only answer your own worksheet responses\",\n });\n }\n\n const question = await findWorksheetQuestionByIdInWorksheet(\n questionId,\n worksheetResponse.worksheetId\n );\n if (!question) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Worksheet question not found\",\n });\n }\n\n const isMarkableByAlgo =\n question.type === \"MULTIPLE_CHOICE\" || question.type === \"TRUE_FALSE\";\n const marksAwardedIfCorrect = question.points || 0;\n const correctAnswer = isMarkableByAlgo\n ? question.type === \"MULTIPLE_CHOICE\"\n ? (question.options as MCQOptions)?.find((o) => o.isCorrect)?.id\n : question.answer?.toString()\n : null;\n\n const existingResponse = worksheetResponse.responses[0];\n\n if (existingResponse) {\n await updateStudentQuestionProgress(existingResponse.id, {\n response,\n ...(isMarkableByAlgo && {\n isCorrect: response === correctAnswer,\n }),\n ...(isMarkableByAlgo && {\n points: response === correctAnswer ? marksAwardedIfCorrect : 0,\n }),\n status: JobStatus.NOT_STARTED,\n });\n } else {\n await createStudentQuestionProgress({\n studentId: worksheetResponse.studentId,\n questionId,\n response,\n studentWorksheetResponseId: worksheetResponseId,\n ...(isMarkableByAlgo && {\n isCorrect: response === correctAnswer,\n }),\n ...(isMarkableByAlgo && {\n points: response === correctAnswer ? marksAwardedIfCorrect : 0,\n }),\n });\n }\n\n const updated = await prisma.studentWorksheetResponse.findUnique({\n where: { id: worksheetResponseId },\n include: { responses: true },\n });\n return updated!;\n}\n\nexport async function cancelGrading(\n worksheetResponseId: string,\n progressId: string,\n userId: string\n) {\n const progress = await findStudentQuestionProgressByIdForTeacherAndResponse(\n progressId,\n worksheetResponseId,\n userId\n );\n if (!progress) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Only class teachers can cancel worksheet grading\",\n });\n }\n return cancelGradePipeline(worksheetResponseId, progressId);\n}\n\nexport async function regradeQuestion(\n worksheetResponseId: string,\n progressId: string,\n userId: string\n) {\n const progress = await findStudentQuestionProgressByIdForTeacherAndResponse(\n progressId,\n worksheetResponseId,\n userId\n );\n if (!progress) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Only class teachers can regrade worksheet answers\",\n });\n }\n return regradeWorksheetPipeline(worksheetResponseId, progressId);\n}\n\nexport async function gradeAnswer(\n questionId: string,\n studentWorksheetResponseId: string,\n userId: string,\n data: {\n responseId?: string;\n response?: string;\n isCorrect: boolean;\n feedback?: string;\n markschemeState?: object;\n points?: number;\n }\n) {\n const worksheetResponse = await findWorksheetResponseByIdForTeacher(\n studentWorksheetResponseId,\n userId\n );\n if (!worksheetResponse) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Only class teachers can grade worksheet answers\",\n });\n }\n\n const question = await findWorksheetQuestionByIdInWorksheet(\n questionId,\n worksheetResponse.worksheetId\n );\n if (!question) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Worksheet question not found\",\n });\n }\n\n if (data.responseId) {\n const existingProgress = await findStudentQuestionProgressByIdForTeacher(\n data.responseId,\n userId\n );\n if (\n !existingProgress ||\n existingProgress.studentWorksheetResponseId !== studentWorksheetResponseId ||\n existingProgress.questionId !== questionId\n ) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Only class teachers can grade worksheet answers\",\n });\n }\n\n return updateStudentQuestionProgressForGrading(data.responseId, {\n isCorrect: data.isCorrect,\n feedback: data.feedback,\n markschemeState: data.markschemeState,\n points: data.points,\n });\n }\n\n const existing = await findStudentQuestionProgress(\n worksheetResponse.studentId,\n questionId,\n studentWorksheetResponseId\n );\n\n if (existing) {\n return updateStudentQuestionProgressForGrading(existing.id, {\n isCorrect: data.isCorrect,\n response: data.response,\n feedback: data.feedback,\n markschemeState: data.markschemeState,\n points: data.points,\n });\n }\n\n return createStudentQuestionProgressForGrading({\n studentId: worksheetResponse.studentId,\n questionId,\n studentWorksheetResponseId,\n response: data.response || \"\",\n isCorrect: data.isCorrect,\n feedback: data.feedback,\n markschemeState: data.markschemeState,\n points: data.points || 0,\n });\n}\n\nexport async function addCommentToResponse(\n responseId: string,\n comment: string,\n authorId: string\n) {\n const progress = await findStudentQuestionProgressByIdForTeacher(\n responseId,\n authorId\n );\n if (!progress) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Only class teachers can comment on worksheet responses\",\n });\n }\n\n return createComment({\n studentQuestionProgressId: responseId,\n content: comment,\n authorId,\n });\n}\n"],"names":[],"mappings":"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","debug_id":"d5088046-353b-5b26-b9c3-10f78d48d45e"}
1
+ {"version":3,"file":"worksheet.js","sources":["services/worksheet.ts"],"sourceRoot":"/","sourcesContent":["/**\n * Worksheet service – CRUD for worksheets, questions, submissions, and AI grading.\n * Integrates with gradeWorksheet pipeline for auto-grading.\n */\nimport { TRPCError } from \"@trpc/server\";\nimport { JobStatus, type WorksheetQuestionType } from \"@prisma/client\";\nimport { prisma } from \"../lib/prisma.js\";\nimport { getFileUrl } from \"../lib/fileUpload.js\";\nimport { cancelGradePipeline, regradeWorksheetPipeline } from \"../pipelines/gradeWorksheet.js\";\nimport { enqueueRunJob } from \"../workers/queues.js\";\nimport { logger } from \"../utils/logger.js\";\nimport {\n findWorksheetByIdMinimal,\n findWorksheetByIdForClassMember,\n findWorksheetByIdForTeacher,\n findWorksheetsByClassId,\n createWorksheet,\n duplicateWorksheet,\n updateWorksheet,\n deleteWorksheet,\n findQuestionsByWorksheetId,\n createWorksheetQuestion,\n updateWorksheetQuestion,\n deleteWorksheetQuestion,\n findSubmissionByIdForWorksheetAccess,\n findOrCreateWorksheetResponse,\n findWorksheetResponseByIdForTeacher,\n findWorksheetResponseWithResponsesForStudent,\n findWorksheetQuestionByIdForTeacher,\n findWorksheetQuestionByIdInWorksheet,\n updateStudentQuestionProgress,\n createStudentQuestionProgress,\n findStudentQuestionProgress,\n updateStudentQuestionProgressForGrading,\n createStudentQuestionProgressForGrading,\n createComment,\n findStudentQuestionProgressByIdForTeacher,\n findStudentQuestionProgressByIdForStudentOrTeacher,\n findStudentQuestionProgressByIdForTeacherAndResponse,\n} from \"../models/worksheet.js\";\n\ntype MCQOptions = { id: string; text: string; isCorrect: boolean }[];\n\nconst sanitizeQuestionOptionsForStudent = (options: unknown) => {\n if (!Array.isArray(options)) return options;\n\n return options.map((option) => {\n if (!option || typeof option !== \"object\") return option;\n\n const { isCorrect: _isCorrect, ...rest } = option as Record<string, unknown>;\n return rest;\n });\n};\n\nconst sanitizeWorksheetForStudent = <T extends {\n questions: Array<Record<string, unknown>>;\n}>(worksheet: T) => ({\n ...worksheet,\n questions: worksheet.questions.map((question) => ({\n ...question,\n answer: \"\",\n markScheme: null,\n options: sanitizeQuestionOptionsForStudent(question.options),\n })),\n});\n\n/** Get worksheet with questions and mark schemes. */\nexport async function getWorksheet(worksheetId: string, userId: string) {\n const teacherWorksheet = await findWorksheetByIdForTeacher(worksheetId, userId);\n if (teacherWorksheet) {\n return teacherWorksheet;\n }\n\n const studentWorksheet = await findWorksheetByIdForClassMember(worksheetId, userId);\n if (!studentWorksheet) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"You do not have access to this worksheet\",\n });\n }\n\n return sanitizeWorksheetForStudent(studentWorksheet);\n}\n\n/** Check if worksheet exists. */\nexport async function worksheetExists(id: string) {\n const worksheet = await findWorksheetByIdMinimal(id);\n return !!worksheet;\n}\n\nexport async function listWorksheets(classId: string) {\n const worksheets = await findWorksheetsByClassId(classId);\n return worksheets.map((w) => ({\n ...w,\n questionCount: w.questions.length,\n }));\n}\n\nexport async function updateWorksheetRecord(\n worksheetId: string,\n userId: string,\n data: { name?: string }\n) {\n const worksheet = await findWorksheetByIdForTeacher(worksheetId, userId);\n if (!worksheet) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Only class teachers can update worksheets\",\n });\n }\n return updateWorksheet(worksheetId, data);\n}\n\nexport async function deleteWorksheetRecord(worksheetId: string, userId: string) {\n const worksheet = await findWorksheetByIdForTeacher(worksheetId, userId);\n if (!worksheet) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Only class teachers can delete worksheets\",\n });\n }\n return deleteWorksheet(worksheetId);\n}\n\nexport async function createWorksheetRecord(classId: string, name: string) {\n return createWorksheet({ classId, name });\n}\n\n/**\n * Duplicate a worksheet into another class. Caller must teach both the source worksheet's class\n * and the target class.\n */\nexport async function duplicateWorksheetRecord(\n worksheetId: string,\n classId: string,\n name: string,\n userId: string\n) {\n const source = await findWorksheetByIdForTeacher(worksheetId, userId);\n if (!source) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message:\n \"Worksheet not found or you are not a teacher of the class it belongs to\",\n });\n }\n\n const targetOk = await prisma.class.findFirst({\n where: {\n id: classId,\n teachers: { some: { id: userId } },\n },\n select: { id: true },\n });\n\n if (!targetOk) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"You are not a teacher of the target class\",\n });\n }\n\n const created = await duplicateWorksheet(worksheetId, classId, name);\n if (!created) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Worksheet not found\",\n });\n }\n\n return created;\n}\n\n/**\n * Kick off PDF-to-worksheet conversion asynchronously.\n *\n * We synchronously create a placeholder Worksheet row (status PENDING) so the\n * caller has an id to navigate to immediately, then enqueue a BullMQ job that\n * fills in the real title and questions. Progress is broadcast on the\n * per-worksheet job Pusher channel; clients should subscribe there to drive\n * loading UI.\n */\nexport async function createWorksheetFromPdf(\n classId: string,\n url: string,\n userId?: string,\n options: { name?: string } = {},\n) {\n const placeholder = await prisma.worksheet.create({\n data: {\n classId,\n name: options.name?.trim() || \"Converting PDF…\",\n status: JobStatus.PENDING,\n },\n });\n\n try {\n const { runJobId } = await enqueueRunJob(\n \"PDF_TO_WORKSHEET\",\n { gcsUrl: url, classId, worksheetId: placeholder.id },\n { classId, userId: userId ?? null },\n );\n\n await prisma.worksheet\n .update({\n where: { id: placeholder.id },\n data: { runJobId },\n })\n .catch((linkError) => {\n logger.error(\n \"createWorksheetFromPdf: failed to link RunJob to Worksheet\",\n {\n worksheetId: placeholder.id,\n runJobId,\n error:\n linkError instanceof Error\n ? linkError.message\n : String(linkError),\n },\n );\n });\n\n return {\n ...placeholder,\n runJobId,\n };\n } catch (error) {\n await prisma.worksheet\n .update({\n where: { id: placeholder.id },\n data: { status: JobStatus.FAILED },\n })\n .catch(() => undefined);\n\n logger.error(\"createWorksheetFromPdf: failed to enqueue conversion\", {\n worksheetId: placeholder.id,\n classId,\n error: error instanceof Error ? error.message : String(error),\n });\n\n throw new TRPCError({\n code: \"INTERNAL_SERVER_ERROR\",\n message: \"Failed to start worksheet conversion\",\n });\n }\n}\n\n/**\n * Create a worksheet from a PDF that was uploaded through the direct-upload flow.\n */\nexport async function createWorksheetFromUploadedPdf(\n classId: string,\n fileId: string,\n userId: string,\n options: { name?: string } = {},\n) {\n const file = await prisma.file.findFirst({\n where: {\n id: fileId,\n userId,\n uploadStatus: \"COMPLETED\",\n },\n select: {\n id: true,\n path: true,\n type: true,\n name: true,\n },\n });\n\n if (!file) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Uploaded file not found or not ready\",\n });\n }\n\n const isPdf =\n file.type === \"application/pdf\" || file.path.toLowerCase().endsWith(\".pdf\");\n if (!isPdf) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Only PDF files can be converted into worksheets\",\n });\n }\n\n const signedUrl = await getFileUrl(file.path);\n return createWorksheetFromPdf(classId, signedUrl, userId, {\n name: options.name ?? file.name ?? undefined,\n });\n}\n\nexport async function addQuestionToWorksheet(\n worksheetId: string,\n userId: string,\n data: {\n question: string;\n answer: string;\n points?: number;\n options?: object;\n markScheme?: object;\n type: WorksheetQuestionType;\n }\n) {\n const worksheet = await findWorksheetByIdForTeacher(worksheetId, userId);\n if (!worksheet) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Only class teachers can modify worksheet questions\",\n });\n }\n return createWorksheetQuestion({\n worksheetId,\n ...data,\n });\n}\n\nexport async function reorderWorksheetQuestions(\n worksheetId: string,\n userId: string,\n movedId: string,\n position: \"before\" | \"after\",\n targetId: string\n) {\n const worksheet = await findWorksheetByIdForTeacher(worksheetId, userId);\n if (!worksheet) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Only class teachers can reorder worksheet questions\",\n });\n }\n\n const questions = await findQuestionsByWorksheetId(worksheetId);\n const movedIdx = questions.findIndex((q) => q.id === movedId);\n if (movedIdx === -1) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Moved question not found\",\n });\n }\n const targetIdx = questions.findIndex((q) => q.id === targetId);\n if (targetIdx === -1) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Target question not found\",\n });\n }\n if (movedId === targetId) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Moved question and target question must be different\",\n });\n }\n\n const withoutMoved = questions.filter((q) => q.id !== movedId);\n const targetIdxWithoutMoved = withoutMoved.findIndex((q) => q.id === targetId);\n const insertIdx =\n position === \"before\" ? targetIdxWithoutMoved : targetIdxWithoutMoved + 1;\n const next = [\n ...withoutMoved.slice(0, insertIdx).map((item) => ({ id: item.id })),\n { id: movedId },\n ...withoutMoved.slice(insertIdx).map((item) => ({ id: item.id })),\n ];\n\n await prisma.$transaction(\n next.map((item, index) =>\n prisma.worksheetQuestion.update({\n where: { id: item.id },\n data: { order: index },\n })\n )\n );\n return next;\n}\n\nexport async function updateWorksheetQuestionRecord(\n worksheetId: string,\n questionId: string,\n userId: string,\n data: {\n question?: string;\n answer?: string;\n points?: number;\n options?: object;\n markScheme?: object;\n type?: WorksheetQuestionType;\n }\n) {\n const question = await findWorksheetQuestionByIdForTeacher(questionId, userId);\n if (!question || question.worksheetId !== worksheetId) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Only class teachers can update worksheet questions\",\n });\n }\n return updateWorksheetQuestion(questionId, data);\n}\n\nexport async function deleteWorksheetQuestionRecord(\n worksheetId: string,\n questionId: string,\n userId: string\n) {\n const question = await findWorksheetQuestionByIdForTeacher(questionId, userId);\n if (!question || question.worksheetId !== worksheetId) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Only class teachers can delete worksheet questions\",\n });\n }\n return deleteWorksheetQuestion(questionId);\n}\n\nexport async function getWorksheetSubmission(\n worksheetId: string,\n submissionId: string,\n userId: string\n) {\n const worksheet = await findWorksheetByIdForClassMember(worksheetId, userId);\n if (!worksheet) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"You do not have access to this worksheet\",\n });\n }\n\n const submission = await findSubmissionByIdForWorksheetAccess(\n submissionId,\n worksheetId,\n worksheet.classId,\n userId\n );\n if (!submission) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"You do not have access to this worksheet submission\",\n });\n }\n return findOrCreateWorksheetResponse(\n submissionId,\n worksheetId,\n submission.studentId\n );\n}\n\nexport async function answerWorksheetQuestion(\n worksheetResponseId: string,\n questionId: string,\n response: string,\n userId: string\n) {\n const worksheetResponse = await findWorksheetResponseWithResponsesForStudent(\n worksheetResponseId,\n userId,\n questionId\n );\n if (!worksheetResponse) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"You can only answer your own worksheet responses\",\n });\n }\n\n const question = await findWorksheetQuestionByIdInWorksheet(\n questionId,\n worksheetResponse.worksheetId\n );\n if (!question) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Worksheet question not found\",\n });\n }\n\n const isMarkableByAlgo =\n question.type === \"MULTIPLE_CHOICE\" || question.type === \"TRUE_FALSE\";\n const marksAwardedIfCorrect = question.points || 0;\n const correctAnswer = isMarkableByAlgo\n ? question.type === \"MULTIPLE_CHOICE\"\n ? (question.options as MCQOptions)?.find((o) => o.isCorrect)?.id\n : question.answer?.toString()\n : null;\n\n const existingResponse = worksheetResponse.responses[0];\n\n if (existingResponse) {\n await updateStudentQuestionProgress(existingResponse.id, {\n response,\n ...(isMarkableByAlgo && {\n isCorrect: response === correctAnswer,\n }),\n ...(isMarkableByAlgo && {\n points: response === correctAnswer ? marksAwardedIfCorrect : 0,\n }),\n status: JobStatus.NOT_STARTED,\n });\n } else {\n await createStudentQuestionProgress({\n studentId: worksheetResponse.studentId,\n questionId,\n response,\n studentWorksheetResponseId: worksheetResponseId,\n ...(isMarkableByAlgo && {\n isCorrect: response === correctAnswer,\n }),\n ...(isMarkableByAlgo && {\n points: response === correctAnswer ? marksAwardedIfCorrect : 0,\n }),\n });\n }\n\n const updated = await prisma.studentWorksheetResponse.findUnique({\n where: { id: worksheetResponseId },\n include: { responses: true },\n });\n return updated!;\n}\n\nexport async function cancelGrading(\n worksheetResponseId: string,\n progressId: string,\n userId: string\n) {\n const progress = await findStudentQuestionProgressByIdForTeacherAndResponse(\n progressId,\n worksheetResponseId,\n userId\n );\n if (!progress) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Only class teachers can cancel worksheet grading\",\n });\n }\n return cancelGradePipeline(worksheetResponseId, progressId);\n}\n\nexport async function regradeQuestion(\n worksheetResponseId: string,\n progressId: string,\n userId: string\n) {\n const progress = await findStudentQuestionProgressByIdForTeacherAndResponse(\n progressId,\n worksheetResponseId,\n userId\n );\n if (!progress) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Only class teachers can regrade worksheet answers\",\n });\n }\n return regradeWorksheetPipeline(worksheetResponseId, progressId);\n}\n\nexport async function gradeAnswer(\n questionId: string,\n studentWorksheetResponseId: string,\n userId: string,\n data: {\n responseId?: string;\n response?: string;\n isCorrect: boolean;\n feedback?: string;\n markschemeState?: object;\n points?: number;\n }\n) {\n const worksheetResponse = await findWorksheetResponseByIdForTeacher(\n studentWorksheetResponseId,\n userId\n );\n if (!worksheetResponse) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Only class teachers can grade worksheet answers\",\n });\n }\n\n const question = await findWorksheetQuestionByIdInWorksheet(\n questionId,\n worksheetResponse.worksheetId\n );\n if (!question) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Worksheet question not found\",\n });\n }\n\n if (data.responseId) {\n const existingProgress = await findStudentQuestionProgressByIdForTeacher(\n data.responseId,\n userId\n );\n if (\n !existingProgress ||\n existingProgress.studentWorksheetResponseId !== studentWorksheetResponseId ||\n existingProgress.questionId !== questionId\n ) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Only class teachers can grade worksheet answers\",\n });\n }\n\n return updateStudentQuestionProgressForGrading(data.responseId, {\n isCorrect: data.isCorrect,\n feedback: data.feedback,\n markschemeState: data.markschemeState,\n points: data.points,\n });\n }\n\n const existing = await findStudentQuestionProgress(\n worksheetResponse.studentId,\n questionId,\n studentWorksheetResponseId\n );\n\n if (existing) {\n return updateStudentQuestionProgressForGrading(existing.id, {\n isCorrect: data.isCorrect,\n response: data.response,\n feedback: data.feedback,\n markschemeState: data.markschemeState,\n points: data.points,\n });\n }\n\n return createStudentQuestionProgressForGrading({\n studentId: worksheetResponse.studentId,\n questionId,\n studentWorksheetResponseId,\n response: data.response || \"\",\n isCorrect: data.isCorrect,\n feedback: data.feedback,\n markschemeState: data.markschemeState,\n points: data.points || 0,\n });\n}\n\nexport async function addCommentToResponse(\n responseId: string,\n comment: string,\n authorId: string\n) {\n const progress = await findStudentQuestionProgressByIdForStudentOrTeacher(\n responseId,\n authorId\n );\n if (!progress) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message:\n \"You can only comment on your own worksheet answers or on answers for classes you teach\",\n });\n }\n\n return createComment({\n studentQuestionProgressId: responseId,\n content: comment,\n authorId,\n });\n}\n"],"names":[],"mappings":"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","debug_id":"dbf4168b-2ccd-58fc-80da-0cb74d76cf44"}
package/dist/trpc.d.ts CHANGED
@@ -74,6 +74,18 @@ export declare const createTRPCRouter: import("@trpc/server").TRPCRouterBuilder<
74
74
  }>;
75
75
  export declare const publicProcedure: import("@trpc/server").TRPCProcedureBuilder<Context, object, {}, import("@trpc/server").TRPCUnsetMarker, import("@trpc/server").TRPCUnsetMarker, import("@trpc/server").TRPCUnsetMarker, import("@trpc/server").TRPCUnsetMarker, false>;
76
76
  export declare const protectedProcedure: import("@trpc/server").TRPCProcedureBuilder<Context, object, {}, import("@trpc/server").TRPCUnsetMarker, import("@trpc/server").TRPCUnsetMarker, import("@trpc/server").TRPCUnsetMarker, import("@trpc/server").TRPCUnsetMarker, false>;
77
+ /** Authenticated + env allowlist (see `assertAdminPanelUser`). */
78
+ export declare const adminProcedure: import("@trpc/server").TRPCProcedureBuilder<Context, object, {
79
+ meta: {
80
+ classId?: string;
81
+ institutionId?: string;
82
+ } | undefined;
83
+ user: {
84
+ id: string;
85
+ } | null;
86
+ req: Request<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>;
87
+ res: Response<any, Record<string, any>>;
88
+ }, import("@trpc/server").TRPCUnsetMarker, import("@trpc/server").TRPCUnsetMarker, import("@trpc/server").TRPCUnsetMarker, import("@trpc/server").TRPCUnsetMarker, false>;
77
89
  export declare const protectedClassMemberProcedure: import("@trpc/server").TRPCProcedureBuilder<Context, object, {}, z.objectInputType<{
78
90
  classId: z.ZodString;
79
91
  }, z.ZodTypeAny, "passthrough">, z.objectOutputType<{
@@ -1 +1 @@
1
- {"version":3,"file":"trpc.d.ts","sourceRoot":"/","sources":["trpc.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,SAAS,EAAE,MAAM,cAAc,CAAC;AAKnD,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAqB,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAGnF,UAAU,oBAAoB;IAC5B,GAAG,EAAE,OAAO,CAAC;IACb,GAAG,EAAE,QAAQ,CAAC;CACf;AAED,MAAM,MAAM,OAAO,GAAG;IACpB,GAAG,EAAE,OAAO,CAAC;IACb,GAAG,EAAE,QAAQ,CAAC;IACd,IAAI,EAAE;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAC5B,IAAI,CAAC,EAAE;QACL,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,aAAa,CAAC,EAAE,MAAM,CAAC;KACxB,CAAC;CACH,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAAU,MAAM,oBAAoB,KAAG,OAAO,CAAC,OAAO,CAmBnF,CAAC;AAEF,eAAO,MAAM,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA8BZ,CAAC;AAOH,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;EAAW,CAAC;AACzC,eAAO,MAAM,eAAe,yOAAqC,CAAC;AAIlE,eAAO,MAAM,kBAAkB,yOAAgC,CAAC;AAEhE,eAAO,MAAM,6BAA6B;;;;uHAEnB,CAAC;AAExB,eAAO,MAAM,yBAAyB;;;;uHAEd,CAAC;AAIzB,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;EAAwB,CAAC"}
1
+ {"version":3,"file":"trpc.d.ts","sourceRoot":"/","sources":["trpc.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,SAAS,EAAE,MAAM,cAAc,CAAC;AAKnD,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAqB,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAInF,UAAU,oBAAoB;IAC5B,GAAG,EAAE,OAAO,CAAC;IACb,GAAG,EAAE,QAAQ,CAAC;CACf;AAED,MAAM,MAAM,OAAO,GAAG;IACpB,GAAG,EAAE,OAAO,CAAC;IACb,GAAG,EAAE,QAAQ,CAAC;IACd,IAAI,EAAE;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAC5B,IAAI,CAAC,EAAE;QACL,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,aAAa,CAAC,EAAE,MAAM,CAAC;KACxB,CAAC;CACH,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAAU,MAAM,oBAAoB,KAAG,OAAO,CAAC,OAAO,CAmBnF,CAAC;AAEF,eAAO,MAAM,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA8BZ,CAAC;AAOH,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;EAAW,CAAC;AACzC,eAAO,MAAM,eAAe,yOAAqC,CAAC;AAIlE,eAAO,MAAM,kBAAkB,yOAAgC,CAAC;AAahE,kEAAkE;AAClE,eAAO,MAAM,cAAc;;kBAlFb,MAAM;wBACA,MAAM;;;YAHZ,MAAM;;;;yKAoFsD,CAAC;AAE3E,eAAO,MAAM,6BAA6B;;;;uHAEnB,CAAC;AAExB,eAAO,MAAM,yBAAyB;;;;uHAEd,CAAC;AAIzB,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;EAAwB,CAAC"}
package/dist/trpc.js CHANGED
@@ -1,6 +1,6 @@
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]="6b11f8e8-b33f-5cae-aab3-08d27c3e81e9")}catch(e){}}();
3
- import { initTRPC } from '@trpc/server';
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]="29e1f1ae-1333-5312-bcdb-8c944e9f0f07")}catch(e){}}();
3
+ import { initTRPC, TRPCError } from '@trpc/server';
4
4
  import { ZodError } from 'zod';
5
5
  import { logger } from './utils/logger.js';
6
6
  import { createLoggingMiddleware } from './middleware/logging.js';
@@ -8,6 +8,7 @@ import { createAuthMiddleware } from './middleware/auth.js';
8
8
  import { z } from 'zod';
9
9
  import { handlePrismaError } from './utils/prismaErrorHandler.js';
10
10
  import { findUserBySessionToken } from './models/auth.js';
11
+ import { assertAdminPanelUser } from './utils/adminPanelAccess.js';
11
12
  export const createTRPCContext = async (opts) => {
12
13
  const { req, res } = opts;
13
14
  const userHeader = req.headers["x-user"];
@@ -62,6 +63,18 @@ export const createTRPCRouter = t.router;
62
63
  export const publicProcedure = t.procedure.use(loggingMiddleware);
63
64
  // Protected procedures
64
65
  export const protectedProcedure = publicProcedure.use(isAuthed);
66
+ const adminPanelMiddleware = t.middleware(({ ctx, next }) => {
67
+ if (!ctx.user) {
68
+ throw new TRPCError({
69
+ code: 'UNAUTHORIZED',
70
+ message: 'Not authenticated',
71
+ });
72
+ }
73
+ assertAdminPanelUser(ctx.user.id);
74
+ return next({ ctx });
75
+ });
76
+ /** Authenticated + env allowlist (see `assertAdminPanelUser`). */
77
+ export const adminProcedure = protectedProcedure.use(adminPanelMiddleware);
65
78
  export const protectedClassMemberProcedure = protectedProcedure
66
79
  .input(z.object({ classId: z.string() }).passthrough())
67
80
  .use(isMemberInClass);
@@ -71,4 +84,4 @@ export const protectedTeacherProcedure = protectedProcedure
71
84
  // Create caller factory
72
85
  export const createCallerFactory = t.createCallerFactory;
73
86
  //# sourceMappingURL=trpc.js.map
74
- //# debugId=6b11f8e8-b33f-5cae-aab3-08d27c3e81e9
87
+ //# debugId=29e1f1ae-1333-5312-bcdb-8c944e9f0f07