@studious-lms/server 1.4.1 → 1.4.3

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 (248) hide show
  1. package/.coderabbit.yaml +4 -1
  2. package/.env.example +6 -0
  3. package/.env.test.example +2 -0
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +101 -76
  6. package/dist/index.js.map +1 -1
  7. package/dist/lib/config/cors.d.ts +16 -0
  8. package/dist/lib/config/cors.d.ts.map +1 -0
  9. package/dist/lib/config/cors.js +75 -0
  10. package/dist/lib/config/cors.js.map +1 -0
  11. package/dist/lib/config/env.d.ts +14 -0
  12. package/dist/lib/config/env.d.ts.map +1 -1
  13. package/dist/lib/config/env.js +9 -2
  14. package/dist/lib/config/env.js.map +1 -1
  15. package/dist/lib/fileUpload.d.ts +10 -2
  16. package/dist/lib/fileUpload.d.ts.map +1 -1
  17. package/dist/lib/fileUpload.js +38 -6
  18. package/dist/lib/fileUpload.js.map +1 -1
  19. package/dist/lib/prisma.d.ts +14 -2
  20. package/dist/lib/prisma.d.ts.map +1 -1
  21. package/dist/lib/prisma.js +27 -8
  22. package/dist/lib/prisma.js.map +1 -1
  23. package/dist/middleware/auth.d.ts.map +1 -1
  24. package/dist/middleware/auth.js +18 -5
  25. package/dist/middleware/auth.js.map +1 -1
  26. package/dist/middleware/security.d.ts.map +1 -1
  27. package/dist/middleware/security.js +3 -3
  28. package/dist/middleware/security.js.map +1 -1
  29. package/dist/models/agenda.d.ts +16 -16
  30. package/dist/models/announcement.d.ts +59 -23
  31. package/dist/models/announcement.d.ts.map +1 -1
  32. package/dist/models/assignment.d.ts +363 -276
  33. package/dist/models/assignment.d.ts.map +1 -1
  34. package/dist/models/attendance.d.ts +63 -21
  35. package/dist/models/attendance.d.ts.map +1 -1
  36. package/dist/models/auth.d.ts +115 -26
  37. package/dist/models/auth.d.ts.map +1 -1
  38. package/dist/models/auth.js +53 -30
  39. package/dist/models/auth.js.map +1 -1
  40. package/dist/models/class.d.ts +115 -65
  41. package/dist/models/class.d.ts.map +1 -1
  42. package/dist/models/class.js +16 -4
  43. package/dist/models/class.js.map +1 -1
  44. package/dist/models/comment.d.ts +52 -16
  45. package/dist/models/comment.d.ts.map +1 -1
  46. package/dist/models/conversation.d.ts +46 -16
  47. package/dist/models/conversation.d.ts.map +1 -1
  48. package/dist/models/event.d.ts +107 -53
  49. package/dist/models/event.d.ts.map +1 -1
  50. package/dist/models/file.d.ts +213 -165
  51. package/dist/models/file.d.ts.map +1 -1
  52. package/dist/models/folder.d.ts +161 -77
  53. package/dist/models/folder.d.ts.map +1 -1
  54. package/dist/models/labChat.d.ts +73 -31
  55. package/dist/models/labChat.d.ts.map +1 -1
  56. package/dist/models/marketing.d.ts +25 -7
  57. package/dist/models/marketing.d.ts.map +1 -1
  58. package/dist/models/message.d.ts +31 -13
  59. package/dist/models/message.d.ts.map +1 -1
  60. package/dist/models/newtonChat.d.ts +34 -10
  61. package/dist/models/newtonChat.d.ts.map +1 -1
  62. package/dist/models/notification.d.ts +29 -10
  63. package/dist/models/notification.d.ts.map +1 -1
  64. package/dist/models/notification.js +13 -9
  65. package/dist/models/notification.js.map +1 -1
  66. package/dist/models/section.d.ts +71 -23
  67. package/dist/models/section.d.ts.map +1 -1
  68. package/dist/models/user.d.ts +27 -9
  69. package/dist/models/user.d.ts.map +1 -1
  70. package/dist/models/worksheet.d.ts +606 -106
  71. package/dist/models/worksheet.d.ts.map +1 -1
  72. package/dist/models/worksheet.js +168 -2
  73. package/dist/models/worksheet.js.map +1 -1
  74. package/dist/pipelines/aiLabChat.d.ts +22 -2
  75. package/dist/pipelines/aiLabChat.d.ts.map +1 -1
  76. package/dist/pipelines/aiLabChat.js +133 -95
  77. package/dist/pipelines/aiLabChat.js.map +1 -1
  78. package/dist/pipelines/aiLabChatContract.d.ts +25 -25
  79. package/dist/pipelines/aiNewtonChat.d.ts.map +1 -1
  80. package/dist/pipelines/aiNewtonChat.js +25 -14
  81. package/dist/pipelines/aiNewtonChat.js.map +1 -1
  82. package/dist/pipelines/gradeWorksheet.d.ts +8 -8
  83. package/dist/pipelines/gradeWorksheet.d.ts.map +1 -1
  84. package/dist/pipelines/gradeWorksheet.js +12 -5
  85. package/dist/pipelines/gradeWorksheet.js.map +1 -1
  86. package/dist/pipelines/labChatPrompt.d.ts +27 -0
  87. package/dist/pipelines/labChatPrompt.d.ts.map +1 -1
  88. package/dist/pipelines/labChatPrompt.js +143 -69
  89. package/dist/pipelines/labChatPrompt.js.map +1 -1
  90. package/dist/routers/_app.d.ts +1579 -1257
  91. package/dist/routers/_app.d.ts.map +1 -1
  92. package/dist/routers/agenda.d.ts +16 -16
  93. package/dist/routers/announcement.d.ts +19 -19
  94. package/dist/routers/assignment.d.ts +307 -291
  95. package/dist/routers/assignment.d.ts.map +1 -1
  96. package/dist/routers/assignment.js +3 -2
  97. package/dist/routers/assignment.js.map +1 -1
  98. package/dist/routers/attendance.d.ts +7 -7
  99. package/dist/routers/auth.d.ts +1 -1
  100. package/dist/routers/class.d.ts +79 -72
  101. package/dist/routers/class.d.ts.map +1 -1
  102. package/dist/routers/class.js +3 -3
  103. package/dist/routers/class.js.map +1 -1
  104. package/dist/routers/comment.d.ts +6 -6
  105. package/dist/routers/conversation.d.ts +11 -11
  106. package/dist/routers/event.d.ts +35 -35
  107. package/dist/routers/file.d.ts +12 -12
  108. package/dist/routers/folder.d.ts +54 -54
  109. package/dist/routers/labChat.d.ts +18 -12
  110. package/dist/routers/labChat.d.ts.map +1 -1
  111. package/dist/routers/labChat.js +5 -3
  112. package/dist/routers/labChat.js.map +1 -1
  113. package/dist/routers/marketing.d.ts +2 -2
  114. package/dist/routers/message.d.ts +33 -2
  115. package/dist/routers/message.d.ts.map +1 -1
  116. package/dist/routers/message.js +27 -4
  117. package/dist/routers/message.js.map +1 -1
  118. package/dist/routers/newtonChat.d.ts +7 -1
  119. package/dist/routers/newtonChat.d.ts.map +1 -1
  120. package/dist/routers/newtonChat.js +5 -4
  121. package/dist/routers/newtonChat.js.map +1 -1
  122. package/dist/routers/notifications.d.ts +13 -6
  123. package/dist/routers/notifications.d.ts.map +1 -1
  124. package/dist/routers/notifications.js +4 -4
  125. package/dist/routers/notifications.js.map +1 -1
  126. package/dist/routers/section.d.ts +7 -7
  127. package/dist/routers/studentProgress.d.ts +86 -0
  128. package/dist/routers/studentProgress.d.ts.map +1 -1
  129. package/dist/routers/studentProgress.js +14 -4
  130. package/dist/routers/studentProgress.js.map +1 -1
  131. package/dist/routers/user.d.ts +1 -1
  132. package/dist/routers/worksheet.d.ts +64 -62
  133. package/dist/routers/worksheet.d.ts.map +1 -1
  134. package/dist/routers/worksheet.js +18 -16
  135. package/dist/routers/worksheet.js.map +1 -1
  136. package/dist/seedDatabase.d.ts +1 -1
  137. package/dist/services/agenda.d.ts +16 -16
  138. package/dist/services/announcement.d.ts +8 -8
  139. package/dist/services/assignment.d.ts +299 -283
  140. package/dist/services/assignment.d.ts.map +1 -1
  141. package/dist/services/assignment.js +24 -5
  142. package/dist/services/assignment.js.map +1 -1
  143. package/dist/services/attendance.d.ts +7 -7
  144. package/dist/services/auth.d.ts +2 -2
  145. package/dist/services/auth.d.ts.map +1 -1
  146. package/dist/services/auth.js +19 -12
  147. package/dist/services/auth.js.map +1 -1
  148. package/dist/services/class.d.ts +74 -68
  149. package/dist/services/class.d.ts.map +1 -1
  150. package/dist/services/class.js +6 -3
  151. package/dist/services/class.js.map +1 -1
  152. package/dist/services/comment.d.ts +6 -6
  153. package/dist/services/conversation.d.ts +11 -11
  154. package/dist/services/event.d.ts +31 -31
  155. package/dist/services/file.d.ts +12 -12
  156. package/dist/services/file.d.ts.map +1 -1
  157. package/dist/services/file.js +7 -2
  158. package/dist/services/file.js.map +1 -1
  159. package/dist/services/folder.d.ts +52 -52
  160. package/dist/services/labChat.d.ts +18 -12
  161. package/dist/services/labChat.d.ts.map +1 -1
  162. package/dist/services/labChat.js +30 -18
  163. package/dist/services/labChat.js.map +1 -1
  164. package/dist/services/marketing.d.ts +2 -2
  165. package/dist/services/message.d.ts +30 -0
  166. package/dist/services/message.d.ts.map +1 -1
  167. package/dist/services/message.js +97 -21
  168. package/dist/services/message.js.map +1 -1
  169. package/dist/services/newtonChat.d.ts +6 -1
  170. package/dist/services/newtonChat.d.ts.map +1 -1
  171. package/dist/services/newtonChat.js +36 -18
  172. package/dist/services/newtonChat.js.map +1 -1
  173. package/dist/services/notification.d.ts +16 -9
  174. package/dist/services/notification.d.ts.map +1 -1
  175. package/dist/services/notification.js +24 -9
  176. package/dist/services/notification.js.map +1 -1
  177. package/dist/services/section.d.ts +6 -6
  178. package/dist/services/studentProgress.d.ts +75 -0
  179. package/dist/services/studentProgress.d.ts.map +1 -1
  180. package/dist/services/studentProgress.js +300 -107
  181. package/dist/services/studentProgress.js.map +1 -1
  182. package/dist/services/worksheet.d.ts +64 -64
  183. package/dist/services/worksheet.d.ts.map +1 -1
  184. package/dist/services/worksheet.js +138 -54
  185. package/dist/services/worksheet.js.map +1 -1
  186. package/dist/trpc.d.ts.map +1 -1
  187. package/dist/trpc.js +11 -17
  188. package/dist/trpc.js.map +1 -1
  189. package/dist/utils/inference.d.ts +9 -12
  190. package/dist/utils/inference.d.ts.map +1 -1
  191. package/dist/utils/inference.js +135 -95
  192. package/dist/utils/inference.js.map +1 -1
  193. package/package.json +1 -1
  194. package/prisma/migrations/20260410124000_add_submission_recommendation_state/migration.sql +14 -0
  195. package/prisma/migrations/20260415103500_add_session_type/migration.sql +33 -0
  196. package/prisma/migrations/20260415133000_add_ai_usage_logs/migration.sql +19 -0
  197. package/prisma/schema.prisma +41 -1
  198. package/scripts/test-pre-push.ts +6 -2
  199. package/src/index.ts +119 -79
  200. package/src/lib/config/cors.ts +96 -0
  201. package/src/lib/config/env.ts +12 -1
  202. package/src/lib/fileUpload.ts +55 -5
  203. package/src/lib/prisma.ts +25 -6
  204. package/src/middleware/auth.ts +19 -4
  205. package/src/middleware/security.ts +1 -1
  206. package/src/models/auth.ts +59 -29
  207. package/src/models/class.ts +15 -4
  208. package/src/models/notification.ts +13 -7
  209. package/src/models/worksheet.ts +202 -0
  210. package/src/pipelines/aiLabChat.ts +183 -104
  211. package/src/pipelines/aiNewtonChat.ts +24 -14
  212. package/src/pipelines/gradeWorksheet.ts +10 -2
  213. package/src/pipelines/labChatPrompt.ts +196 -68
  214. package/src/routers/assignment.ts +1 -0
  215. package/src/routers/class.ts +2 -2
  216. package/src/routers/labChat.ts +3 -1
  217. package/src/routers/message.ts +38 -1
  218. package/src/routers/newtonChat.ts +4 -2
  219. package/src/routers/notifications.ts +2 -2
  220. package/src/routers/studentProgress.ts +25 -1
  221. package/src/routers/worksheet.ts +31 -19
  222. package/src/services/assignment.ts +30 -2
  223. package/src/services/auth.ts +26 -12
  224. package/src/services/class.ts +4 -1
  225. package/src/services/file.ts +6 -0
  226. package/src/services/labChat.ts +42 -18
  227. package/src/services/message.ts +163 -18
  228. package/src/services/newtonChat.ts +49 -18
  229. package/src/services/notification.ts +27 -8
  230. package/src/services/studentProgress.ts +425 -121
  231. package/src/services/worksheet.ts +191 -51
  232. package/src/trpc.ts +12 -18
  233. package/src/utils/inference.ts +205 -112
  234. package/tests/globalSetup.ts +13 -4
  235. package/tests/lib/cors.test.ts +103 -0
  236. package/tests/pipelines/aiLabChat.test.ts +64 -84
  237. package/tests/routers/auth.test.ts +120 -17
  238. package/tests/routers/class.test.ts +7 -1
  239. package/tests/routers/message.test.ts +236 -9
  240. package/tests/routers/newtonChat.test.ts +81 -0
  241. package/tests/routers/notification.test.ts +12 -0
  242. package/tests/routers/studentProgress.test.ts +2 -31
  243. package/tests/routers/worksheet.test.ts +194 -0
  244. package/tests/setup.ts +22 -5
  245. package/tests/utils/aiLabChatPrompt.test.ts +114 -6
  246. package/tests/utils/inference.test.ts +218 -0
  247. package/tests/utils/studentProgress.test.ts +361 -0
  248. package/vitest.unit.config.ts +1 -0
@@ -2,15 +2,16 @@
2
2
  * Student progress service - assignment recommendations and progress chat.
3
3
  */
4
4
 
5
- !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]="e4b426af-1ccd-5152-a996-9a03a09b6503")}catch(e){}}();
5
+ !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]="9369c0fb-4b1a-5b05-8e92-03f34d46226c")}catch(e){}}();
6
6
  import { TRPCError } from "@trpc/server";
7
7
  import { prisma } from "../lib/prisma.js";
8
+ import { isTeacherInClass } from "../models/class.js";
8
9
  import { inference } from "../utils/inference.js";
9
10
  import { logger } from "../utils/logger.js";
10
- import { isTeacherInClass } from "../models/class.js";
11
11
  function calculatePercentage(submission) {
12
- if (submission.gradeReceived == null || !submission.assignment.maxGrade)
12
+ if (submission.gradeReceived == null || !submission.assignment.maxGrade) {
13
13
  return null;
14
+ }
14
15
  return (Math.round((submission.gradeReceived / submission.assignment.maxGrade) * 1000) / 10);
15
16
  }
16
17
  function calculateTrend(submissions) {
@@ -48,6 +49,183 @@ function getOverallGrade(submissions) {
48
49
  ? Math.round((totalWeighted / totalWeight) * 1000) / 10
49
50
  : null;
50
51
  }
52
+ function normalizeAssignmentType(assignmentType) {
53
+ if (assignmentType === "TEST" || assignmentType === "QUIZ") {
54
+ return "HOMEWORK";
55
+ }
56
+ return assignmentType;
57
+ }
58
+ function buildSuggestedAssignment(recommendationType, submission, displayName, percentage) {
59
+ const sourceTitle = submission.assignment.title;
60
+ const section = submission.assignment.section?.name;
61
+ const titleByType = {
62
+ missing: `Make-up: ${sourceTitle}`,
63
+ low_score: `Targeted practice: ${sourceTitle}`,
64
+ awaiting_feedback: `Revision follow-up: ${sourceTitle}`,
65
+ };
66
+ const leadByType = {
67
+ missing: `${displayName} missed the original assignment and needs a focused make-up task on the same material.`,
68
+ low_score: percentage != null
69
+ ? `${displayName} scored ${percentage}% and needs targeted reinforcement on the underlying concepts.`
70
+ : `${displayName} needs targeted reinforcement on the underlying concepts.`,
71
+ awaiting_feedback: `${displayName} has work in progress here, so a short follow-up can keep momentum once feedback lands.`,
72
+ };
73
+ return {
74
+ title: titleByType[recommendationType],
75
+ instructions: [
76
+ leadByType[recommendationType],
77
+ `Source assignment: ${sourceTitle}.`,
78
+ section ? `Section: ${section}.` : null,
79
+ "Keep the work concise and focused on the specific area of concern.",
80
+ ]
81
+ .filter((line) => Boolean(line))
82
+ .join("\n\n"),
83
+ type: normalizeAssignmentType(submission.assignment.type),
84
+ maxGrade: submission.assignment.maxGrade ?? 100,
85
+ weight: submission.assignment.weight || 1,
86
+ graded: true,
87
+ sectionId: submission.assignment.section?.id ?? null,
88
+ sourceAssignmentId: submission.assignment.id,
89
+ };
90
+ }
91
+ function buildRecommendationCandidate(submission, displayName, now) {
92
+ if (submission.recommendationState === "ASSIGNED" ||
93
+ submission.recommendationState === "DISMISSED") {
94
+ return null;
95
+ }
96
+ const percentage = calculatePercentage(submission);
97
+ const isMissing = !submission.submitted &&
98
+ submission.assignment.dueDate.getTime() < now.getTime();
99
+ const isLowScore = percentage != null && percentage < 70;
100
+ const isUnreturned = Boolean(submission.submitted) &&
101
+ submission.gradeReceived == null &&
102
+ !submission.returned;
103
+ const reasons = [];
104
+ let priorityScore = 0;
105
+ let recommendationType = null;
106
+ if (isMissing) {
107
+ reasons.push("Missing past-due work");
108
+ priorityScore += 100;
109
+ recommendationType = "missing";
110
+ }
111
+ if (isLowScore) {
112
+ reasons.push(`Scored ${percentage}%`);
113
+ priorityScore += 85 - (percentage ?? 0);
114
+ recommendationType ?? (recommendationType = "low_score");
115
+ }
116
+ if (isUnreturned) {
117
+ reasons.push("Awaiting grade or feedback");
118
+ priorityScore += 15;
119
+ recommendationType ?? (recommendationType = "awaiting_feedback");
120
+ }
121
+ if (priorityScore <= 0 || !recommendationType) {
122
+ return null;
123
+ }
124
+ return {
125
+ assignmentId: submission.assignment.id,
126
+ submissionId: submission.id,
127
+ title: submission.assignment.title,
128
+ type: submission.assignment.type,
129
+ sectionId: submission.assignment.section?.id ?? null,
130
+ sectionName: submission.assignment.section?.name ?? null,
131
+ dueDate: submission.assignment.dueDate,
132
+ gradeReceived: submission.gradeReceived,
133
+ maxGrade: submission.assignment.maxGrade,
134
+ percentage,
135
+ submitted: Boolean(submission.submitted),
136
+ returned: Boolean(submission.returned),
137
+ recommendationType,
138
+ reasons,
139
+ priorityScore,
140
+ suggestedAssignment: buildSuggestedAssignment(recommendationType, submission, displayName, percentage),
141
+ };
142
+ }
143
+ function summarizeStudentRecommendations(student, submissions) {
144
+ const now = new Date();
145
+ const displayName = student.profile?.displayName ?? student.username;
146
+ const recommendationCandidates = submissions
147
+ .map((submission) => buildRecommendationCandidate(submission, displayName, now))
148
+ .filter((candidate) => candidate != null)
149
+ .sort((a, b) => b.priorityScore - a.priorityScore);
150
+ const recommendations = recommendationCandidates
151
+ .slice(0, 5)
152
+ .map(({ priorityScore, ...candidate }) => candidate);
153
+ const gradedPercentages = submissions
154
+ .map(calculatePercentage)
155
+ .filter((value) => value != null);
156
+ const lowScoreCount = gradedPercentages.filter((percentage) => percentage < 70).length;
157
+ const missingCount = submissions.filter((submission) => !submission.submitted &&
158
+ submission.assignment.dueDate.getTime() < now.getTime()).length;
159
+ const trend = calculateTrend(submissions);
160
+ const overallGrade = getOverallGrade(submissions);
161
+ const nextSteps = [
162
+ missingCount > 0
163
+ ? `Prioritize ${missingCount} missing assignment${missingCount === 1 ? "" : "s"}.`
164
+ : null,
165
+ lowScoreCount > 0
166
+ ? `Review ${lowScoreCount} low-scoring assignment${lowScoreCount === 1 ? "" : "s"} before introducing new material.`
167
+ : null,
168
+ trend < -5
169
+ ? "Schedule a check-in because recent performance is trending down."
170
+ : null,
171
+ recommendationCandidates.length === 0
172
+ ? "No urgent assignment issues detected from the available grades."
173
+ : null,
174
+ ].filter((step) => Boolean(step));
175
+ return {
176
+ student: {
177
+ id: student.id,
178
+ username: student.username,
179
+ displayName,
180
+ },
181
+ summary: {
182
+ overallGrade,
183
+ trend,
184
+ completedAssignments: submissions.filter((submission) => Boolean(submission.submitted)).length,
185
+ totalAssignments: submissions.length,
186
+ missingCount,
187
+ lowScoreCount,
188
+ },
189
+ recommendations,
190
+ recommendationCandidates,
191
+ nextSteps,
192
+ priorityScore: recommendationCandidates[0]?.priorityScore ?? 0,
193
+ };
194
+ }
195
+ async function syncRecommendationStates(submissions, recommendationCandidates) {
196
+ const candidateSubmissionIds = new Set(recommendationCandidates.map((candidate) => candidate.submissionId));
197
+ const updates = [];
198
+ for (const submission of submissions) {
199
+ const shouldBeOpen = candidateSubmissionIds.has(submission.id);
200
+ if (shouldBeOpen && submission.recommendationState === "NONE") {
201
+ updates.push(prisma.submission.update({
202
+ where: { id: submission.id },
203
+ data: {
204
+ recommendationState: "OPEN",
205
+ recommendationUpdatedAt: new Date(),
206
+ },
207
+ }));
208
+ continue;
209
+ }
210
+ if (!shouldBeOpen && submission.recommendationState === "OPEN") {
211
+ updates.push(prisma.submission.update({
212
+ where: { id: submission.id },
213
+ data: {
214
+ recommendationState: "NONE",
215
+ recommendationUpdatedAt: new Date(),
216
+ },
217
+ }));
218
+ }
219
+ }
220
+ if (updates.length) {
221
+ await Promise.all(updates);
222
+ }
223
+ }
224
+ async function summarizeStudentRecommendationsWithCases(student, submissions) {
225
+ const summary = summarizeStudentRecommendations(student, submissions);
226
+ await syncRecommendationStates(submissions, summary.recommendationCandidates);
227
+ return summary;
228
+ }
51
229
  async function loadStudentProgressContext(viewerId, classId, studentId) {
52
230
  const isTeacher = await isTeacherInClass(classId, viewerId);
53
231
  if (viewerId !== studentId && !isTeacher) {
@@ -106,98 +284,116 @@ async function loadStudentProgressContext(viewerId, classId, studentId) {
106
284
  }
107
285
  export async function getStudentProgressRecommendations(viewerId, classId, studentId) {
108
286
  const { student, submissions } = await loadStudentProgressContext(viewerId, classId, studentId);
109
- const now = new Date();
110
- const recommendationCandidates = submissions
111
- .map((submission) => {
112
- const percentage = calculatePercentage(submission);
113
- const isMissing = !submission.submitted &&
114
- submission.assignment.dueDate.getTime() < now.getTime();
115
- const isLowScore = percentage != null && percentage < 70;
116
- const isUnreturned = Boolean(submission.submitted) &&
117
- submission.gradeReceived == null &&
118
- !submission.returned;
119
- const isUpcoming = !Boolean(submission.submitted) &&
120
- !submission.submittedAt &&
121
- !submission.returned &&
122
- submission.gradeReceived == null &&
123
- submission.assignment.dueDate.getTime() >= now.getTime();
124
- const reasons = [];
125
- let priorityScore = 0;
126
- if (isMissing) {
127
- reasons.push("Missing past-due work");
128
- priorityScore += 100;
129
- }
130
- if (isLowScore) {
131
- reasons.push(`Scored ${percentage}%`);
132
- priorityScore += 85 - (percentage ?? 0);
133
- }
134
- if (isUnreturned) {
135
- reasons.push("Awaiting grade or feedback");
136
- priorityScore += 15;
137
- }
138
- if (isUpcoming) {
139
- reasons.push("Upcoming graded assignment");
140
- priorityScore += 5;
141
- }
142
- return {
143
- assignmentId: submission.assignment.id,
144
- submissionId: submission.id,
145
- title: submission.assignment.title,
146
- type: submission.assignment.type,
147
- sectionName: submission.assignment.section?.name ?? null,
148
- dueDate: submission.assignment.dueDate,
149
- gradeReceived: submission.gradeReceived,
150
- maxGrade: submission.assignment.maxGrade,
151
- percentage,
152
- submitted: Boolean(submission.submitted),
153
- returned: Boolean(submission.returned),
154
- reasons,
155
- priorityScore,
156
- };
157
- })
158
- .filter((candidate) => candidate.priorityScore > 0)
159
- .sort((a, b) => b.priorityScore - a.priorityScore)
160
- .slice(0, 5);
161
- const gradedPercentages = submissions
162
- .map(calculatePercentage)
163
- .filter((value) => value != null);
164
- const lowScoreCount = gradedPercentages.filter((percentage) => percentage < 70).length;
165
- const missingCount = submissions.filter((submission) => !submission.submitted &&
166
- submission.assignment.dueDate.getTime() < now.getTime()).length;
167
- const trend = calculateTrend(submissions);
168
- const overallGrade = getOverallGrade(submissions);
169
- const nextSteps = [
170
- missingCount > 0
171
- ? `Prioritize ${missingCount} missing assignment${missingCount === 1 ? "" : "s"}.`
172
- : null,
173
- lowScoreCount > 0
174
- ? `Review ${lowScoreCount} low-scoring assignment${lowScoreCount === 1 ? "" : "s"} before introducing new material.`
175
- : null,
176
- trend < -5
177
- ? "Schedule a check-in because recent performance is trending down."
178
- : null,
179
- recommendationCandidates.length === 0
180
- ? "No urgent assignment issues detected from the available grades."
181
- : null,
182
- ].filter((step) => Boolean(step));
287
+ const summary = await summarizeStudentRecommendationsWithCases(student, submissions);
288
+ const { recommendationCandidates: _recommendationCandidates, ...summaryResponse } = summary;
183
289
  return {
184
- student: {
185
- id: student.id,
186
- username: student.username,
187
- displayName: student.profile?.displayName ?? student.username,
290
+ student: summaryResponse.student,
291
+ summary: summaryResponse.summary,
292
+ recommendations: summaryResponse.recommendations,
293
+ nextSteps: summaryResponse.nextSteps,
294
+ };
295
+ }
296
+ export async function getClassProgressRecommendations(viewerId, classId) {
297
+ const teacherInClass = await isTeacherInClass(classId, viewerId);
298
+ if (!teacherInClass) {
299
+ throw new TRPCError({
300
+ code: "UNAUTHORIZED",
301
+ message: "Only teachers can view class-wide recommendations",
302
+ });
303
+ }
304
+ const classData = await prisma.class.findUnique({
305
+ where: { id: classId },
306
+ select: {
307
+ id: true,
308
+ name: true,
309
+ subject: true,
310
+ students: {
311
+ select: {
312
+ id: true,
313
+ username: true,
314
+ profile: { select: { displayName: true } },
315
+ submissions: {
316
+ where: {
317
+ assignment: { classId, graded: true },
318
+ },
319
+ include: {
320
+ assignment: {
321
+ select: {
322
+ id: true,
323
+ title: true,
324
+ dueDate: true,
325
+ maxGrade: true,
326
+ weight: true,
327
+ type: true,
328
+ section: { select: { id: true, name: true } },
329
+ },
330
+ },
331
+ },
332
+ orderBy: { assignment: { dueDate: "asc" } },
333
+ },
334
+ },
335
+ },
336
+ },
337
+ });
338
+ if (!classData) {
339
+ throw new TRPCError({ code: "NOT_FOUND", message: "Class not found" });
340
+ }
341
+ const students = (await Promise.all(classData.students.map((student) => summarizeStudentRecommendationsWithCases(student, student.submissions))))
342
+ .map(({ recommendationCandidates: _recommendationCandidates, ...student }) => student)
343
+ .filter((student) => student.recommendations.length > 0)
344
+ .sort((a, b) => b.priorityScore - a.priorityScore);
345
+ const totalRecommendations = students.reduce((sum, student) => sum + student.recommendations.length, 0);
346
+ const studentsWithConcerns = students.length;
347
+ const totalMissingAssignments = students.reduce((sum, student) => sum + student.summary.missingCount, 0);
348
+ const totalLowScoreAssignments = students.reduce((sum, student) => sum + student.summary.lowScoreCount, 0);
349
+ return {
350
+ class: {
351
+ id: classData.id,
352
+ name: classData.name,
353
+ subject: classData.subject,
188
354
  },
189
355
  summary: {
190
- overallGrade,
191
- trend,
192
- completedAssignments: submissions.filter((submission) => Boolean(submission.submitted)).length,
193
- totalAssignments: submissions.length,
194
- missingCount,
195
- lowScoreCount,
356
+ studentsWithConcerns,
357
+ totalRecommendations,
358
+ totalMissingAssignments,
359
+ totalLowScoreAssignments,
196
360
  },
197
- recommendations: recommendationCandidates.map(({ priorityScore, ...candidate }) => candidate),
198
- nextSteps,
361
+ students: students.map(({ priorityScore, ...student }) => student),
199
362
  };
200
363
  }
364
+ export async function dismissStudentRecommendation(viewerId, input) {
365
+ const teacherInClass = await isTeacherInClass(input.classId, viewerId);
366
+ if (!teacherInClass) {
367
+ throw new TRPCError({
368
+ code: "UNAUTHORIZED",
369
+ message: "Only teachers can dismiss recommendations",
370
+ });
371
+ }
372
+ const submission = await prisma.submission.findFirst({
373
+ where: {
374
+ id: input.submissionId,
375
+ studentId: input.studentId,
376
+ assignment: {
377
+ classId: input.classId,
378
+ },
379
+ },
380
+ select: { id: true },
381
+ });
382
+ if (!submission) {
383
+ throw new TRPCError({
384
+ code: "NOT_FOUND",
385
+ message: "Submission not found for this student in this class",
386
+ });
387
+ }
388
+ await prisma.submission.update({
389
+ where: { id: input.submissionId },
390
+ data: {
391
+ recommendationState: "DISMISSED",
392
+ recommendationUpdatedAt: new Date(),
393
+ },
394
+ });
395
+ return { success: true };
396
+ }
201
397
  function buildProgressSummary(submissions) {
202
398
  return submissions.map((submission) => ({
203
399
  title: submission.assignment.title,
@@ -239,7 +435,10 @@ export async function chatAboutStudentProgress(viewerId, input) {
239
435
  { role: "user", content: input.message },
240
436
  ];
241
437
  try {
242
- const response = await inference(messages);
438
+ const response = await inference(messages, undefined, {
439
+ reason: "student_progress_chat",
440
+ metadata: { viewerId, classId: input.classId, studentId: input.studentId },
441
+ });
243
442
  if (typeof response !== "string" || response.trim().length === 0) {
244
443
  throw new Error("Student progress chat returned an empty response");
245
444
  }
@@ -260,32 +459,26 @@ export async function chatAboutStudentProgress(viewerId, input) {
260
459
  const awaitingFeedback = summary.assignments.filter((assignment) => assignment.submitted &&
261
460
  assignment.gradeReceived == null &&
262
461
  !assignment.returned).length;
263
- const trendLabel = summary.overallGrade == null
264
- ? "not enough graded work to determine a recent trend"
265
- : summary.trend > 5
266
- ? "improving"
267
- : summary.trend < -5
268
- ? "declining"
269
- : "stable";
270
- const advice = [
462
+ const fallbackParts = [
463
+ `${displayName}'s current overall grade is ${overall}.`,
271
464
  missingItems > 0
272
- ? `Review ${missingItems} missing assignment${missingItems === 1 ? "" : "s"}.`
465
+ ? `${missingItems} assignment${missingItems === 1 ? "" : "s"} ${missingItems === 1 ? "appears" : "appear"} overdue.`
273
466
  : null,
274
467
  lowScores > 0
275
- ? `Use targeted practice for ${lowScores} low-scoring assignment${lowScores === 1 ? "" : "s"}.`
468
+ ? `${lowScores} graded assignment${lowScores === 1 ? " is" : "s are"} below 70%.`
276
469
  : null,
277
470
  awaitingFeedback > 0
278
- ? `Check back after feedback is returned for ${awaitingFeedback} submitted assignment${awaitingFeedback === 1 ? "" : "s"}.`
279
- : null,
280
- missingItems === 0 && lowScores === 0 && awaitingFeedback === 0
281
- ? "Check upcoming work and ask for feedback as new grades are returned."
471
+ ? `${awaitingFeedback} assignment${awaitingFeedback === 1 ? " is" : "s are"} still awaiting feedback.`
282
472
  : null,
283
- ].filter((item) => Boolean(item));
473
+ summary.trend < -5
474
+ ? "Recent performance is trending down, so a check-in would be reasonable."
475
+ : "Performance looks relatively steady from the available data.",
476
+ ].filter((part) => Boolean(part));
284
477
  return {
285
- message: `${displayName}'s current overall progress is ${overall}, with ${trendLabel}. ${advice.join(" ")}`,
478
+ message: fallbackParts.join(" "),
286
479
  isFallback: true,
287
480
  };
288
481
  }
289
482
  }
290
483
  //# sourceMappingURL=studentProgress.js.map
291
- //# debugId=e4b426af-1ccd-5152-a996-9a03a09b6503
484
+ //# debugId=9369c0fb-4b1a-5b05-8e92-03f34d46226c
@@ -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 { prisma } from \"../lib/prisma.js\";\nimport { inference } from \"../utils/inference.js\";\nimport { logger } from \"../utils/logger.js\";\nimport { isTeacherInClass } from \"../models/class.js\";\nimport type { AssignmentType } from \"@prisma/client\";\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 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\nfunction calculatePercentage(submission: GradeSubmission) {\n if (submission.gradeReceived == null || !submission.assignment.maxGrade)\n return null;\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 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 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\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 now = new Date();\n const recommendationCandidates = submissions\n .map((submission) => {\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 isUpcoming =\n !Boolean(submission.submitted) &&\n !submission.submittedAt &&\n !submission.returned &&\n submission.gradeReceived == null &&\n submission.assignment.dueDate.getTime() >= now.getTime();\n const reasons: string[] = [];\n let priorityScore = 0;\n\n if (isMissing) {\n reasons.push(\"Missing past-due work\");\n priorityScore += 100;\n }\n if (isLowScore) {\n reasons.push(`Scored ${percentage}%`);\n priorityScore += 85 - (percentage ?? 0);\n }\n if (isUnreturned) {\n reasons.push(\"Awaiting grade or feedback\");\n priorityScore += 15;\n }\n if (isUpcoming) {\n reasons.push(\"Upcoming graded assignment\");\n priorityScore += 5;\n }\n\n return {\n assignmentId: submission.assignment.id,\n submissionId: submission.id,\n title: submission.assignment.title,\n type: submission.assignment.type,\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 reasons,\n priorityScore,\n };\n })\n .filter((candidate) => candidate.priorityScore > 0)\n .sort((a, b) => b.priorityScore - a.priorityScore)\n .slice(0, 5);\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: student.profile?.displayName ?? student.username,\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: recommendationCandidates.map(\n ({ priorityScore, ...candidate }) => candidate,\n ),\n nextSteps,\n };\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);\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 const trendLabel =\n summary.overallGrade == null\n ? \"not enough graded work to determine a recent trend\"\n : summary.trend > 5\n ? \"improving\"\n : summary.trend < -5\n ? \"declining\"\n : \"stable\";\n const advice = [\n missingItems > 0\n ? `Review ${missingItems} missing assignment${missingItems === 1 ? \"\" : \"s\"}.`\n : null,\n lowScores > 0\n ? `Use targeted practice for ${lowScores} low-scoring assignment${lowScores === 1 ? \"\" : \"s\"}.`\n : null,\n awaitingFeedback > 0\n ? `Check back after feedback is returned for ${awaitingFeedback} submitted assignment${awaitingFeedback === 1 ? \"\" : \"s\"}.`\n : null,\n missingItems === 0 && lowScores === 0 && awaitingFeedback === 0\n ? \"Check upcoming work and ask for feedback as new grades are returned.\"\n : null,\n ].filter((item): item is string => Boolean(item));\n return {\n message: `${displayName}'s current overall progress is ${overall}, with ${trendLabel}. ${advice.join(\" \")}`,\n isFallback: true,\n };\n }\n}\n"],"names":[],"mappings":"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","debug_id":"e4b426af-1ccd-5152-a996-9a03a09b6503"}
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"}