@studious-lms/server 1.22.0 → 1.24.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 (298) hide show
  1. package/.env.example +0 -1
  2. package/data/curriculum/nepal-cdc-g4-g5/README.md +90 -0
  3. package/data/curriculum/nepal-cdc-g4-g5/manifest.json +97 -0
  4. package/data/curriculum/nepal-cdc-g4-g5/proficiency-scale.json +76 -0
  5. package/data/curriculum/nepal-cdc-g4-g5/subjects/english.json +6936 -0
  6. package/data/curriculum/nepal-cdc-g4-g5/subjects/health-physical-creative-arts.json +3401 -0
  7. package/data/curriculum/nepal-cdc-g4-g5/subjects/mathematics.json +1511 -0
  8. package/data/curriculum/nepal-cdc-g4-g5/subjects/nepali.json +7928 -0
  9. package/data/curriculum/nepal-cdc-g4-g5/subjects/science-technology.json +2420 -0
  10. package/data/curriculum/nepal-cdc-g4-g5/subjects/social-studies.json +2177 -0
  11. package/dist/lib/assignmentTypeDefaults.d.ts +41 -0
  12. package/dist/lib/assignmentTypeDefaults.d.ts.map +1 -0
  13. package/dist/lib/assignmentTypeDefaults.js +167 -0
  14. package/dist/lib/assignmentTypeDefaults.js.map +1 -0
  15. package/dist/lib/config/env.d.ts +0 -8
  16. package/dist/lib/config/env.d.ts.map +1 -1
  17. package/dist/lib/config/env.js +2 -5
  18. package/dist/lib/config/env.js.map +1 -1
  19. package/dist/lib/feePdf.d.ts +19 -0
  20. package/dist/lib/feePdf.d.ts.map +1 -0
  21. package/dist/lib/feePdf.js +167 -0
  22. package/dist/lib/feePdf.js.map +1 -0
  23. package/dist/lib/feePdfFormat.d.ts +4 -0
  24. package/dist/lib/feePdfFormat.d.ts.map +1 -0
  25. package/dist/lib/feePdfFormat.js +16 -0
  26. package/dist/lib/feePdfFormat.js.map +1 -0
  27. package/dist/lib/idCardPdf.d.ts +26 -0
  28. package/dist/lib/idCardPdf.d.ts.map +1 -0
  29. package/dist/lib/idCardPdf.js +272 -0
  30. package/dist/lib/idCardPdf.js.map +1 -0
  31. package/dist/lib/pdfLetterhead.d.ts +41 -0
  32. package/dist/lib/pdfLetterhead.d.ts.map +1 -0
  33. package/dist/lib/pdfLetterhead.js +86 -0
  34. package/dist/lib/pdfLetterhead.js.map +1 -0
  35. package/dist/lib/transcriptPdf.d.ts +55 -0
  36. package/dist/lib/transcriptPdf.d.ts.map +1 -0
  37. package/dist/lib/transcriptPdf.js +303 -0
  38. package/dist/lib/transcriptPdf.js.map +1 -0
  39. package/dist/models/academicCalendar.d.ts +198 -0
  40. package/dist/models/academicCalendar.d.ts.map +1 -0
  41. package/dist/models/academicCalendar.js +106 -0
  42. package/dist/models/academicCalendar.js.map +1 -0
  43. package/dist/models/agenda.d.ts +8 -0
  44. package/dist/models/agenda.d.ts.map +1 -1
  45. package/dist/models/announcement.d.ts +2 -0
  46. package/dist/models/announcement.d.ts.map +1 -1
  47. package/dist/models/assignment.d.ts +66 -0
  48. package/dist/models/assignment.d.ts.map +1 -1
  49. package/dist/models/attendance.d.ts +659 -21
  50. package/dist/models/attendance.d.ts.map +1 -1
  51. package/dist/models/attendance.js +544 -75
  52. package/dist/models/attendance.js.map +1 -1
  53. package/dist/models/auth.d.ts +2 -0
  54. package/dist/models/auth.d.ts.map +1 -1
  55. package/dist/models/class.d.ts +12 -0
  56. package/dist/models/class.d.ts.map +1 -1
  57. package/dist/models/class.js +3 -2
  58. package/dist/models/class.js.map +1 -1
  59. package/dist/models/event.d.ts +16 -0
  60. package/dist/models/event.d.ts.map +1 -1
  61. package/dist/models/fees.d.ts +554 -0
  62. package/dist/models/fees.d.ts.map +1 -0
  63. package/dist/models/fees.js +278 -0
  64. package/dist/models/fees.js.map +1 -0
  65. package/dist/models/file.d.ts +29 -0
  66. package/dist/models/file.d.ts.map +1 -1
  67. package/dist/models/folder.d.ts +2 -0
  68. package/dist/models/folder.d.ts.map +1 -1
  69. package/dist/models/labChat.d.ts +2 -0
  70. package/dist/models/labChat.d.ts.map +1 -1
  71. package/dist/models/marketplace.d.ts +178 -0
  72. package/dist/models/marketplace.d.ts.map +1 -0
  73. package/dist/models/marketplace.js +115 -0
  74. package/dist/models/marketplace.js.map +1 -0
  75. package/dist/models/moduleItem.d.ts +304 -0
  76. package/dist/models/moduleItem.d.ts.map +1 -0
  77. package/dist/models/moduleItem.js +78 -0
  78. package/dist/models/moduleItem.js.map +1 -0
  79. package/dist/models/newtonChat.d.ts +1 -0
  80. package/dist/models/newtonChat.d.ts.map +1 -1
  81. package/dist/models/school.d.ts +193 -0
  82. package/dist/models/school.d.ts.map +1 -1
  83. package/dist/models/school.js +80 -2
  84. package/dist/models/school.js.map +1 -1
  85. package/dist/models/section.d.ts +4 -0
  86. package/dist/models/section.d.ts.map +1 -1
  87. package/dist/models/worksheet.d.ts +19 -0
  88. package/dist/models/worksheet.d.ts.map +1 -1
  89. package/dist/models/worksheet.js +2 -2
  90. package/dist/models/worksheet.js.map +1 -1
  91. package/dist/routers/_app.d.ts +11605 -7133
  92. package/dist/routers/_app.d.ts.map +1 -1
  93. package/dist/routers/_app.js +12 -2
  94. package/dist/routers/_app.js.map +1 -1
  95. package/dist/routers/academicCalendar.d.ts +229 -0
  96. package/dist/routers/academicCalendar.d.ts.map +1 -0
  97. package/dist/routers/academicCalendar.js +75 -0
  98. package/dist/routers/academicCalendar.js.map +1 -0
  99. package/dist/routers/admin.d.ts +2 -0
  100. package/dist/routers/admin.d.ts.map +1 -1
  101. package/dist/routers/agenda.d.ts +8 -0
  102. package/dist/routers/agenda.d.ts.map +1 -1
  103. package/dist/routers/assignment.d.ts +75 -0
  104. package/dist/routers/assignment.d.ts.map +1 -1
  105. package/dist/routers/assignment.js +10 -2
  106. package/dist/routers/assignment.js.map +1 -1
  107. package/dist/routers/attendance.d.ts +103 -0
  108. package/dist/routers/attendance.d.ts.map +1 -1
  109. package/dist/routers/attendance.js +8 -5
  110. package/dist/routers/attendance.js.map +1 -1
  111. package/dist/routers/class.d.ts +27 -0
  112. package/dist/routers/class.d.ts.map +1 -1
  113. package/dist/routers/class.js +6 -2
  114. package/dist/routers/class.js.map +1 -1
  115. package/dist/routers/event.d.ts +8 -0
  116. package/dist/routers/event.d.ts.map +1 -1
  117. package/dist/routers/fees.d.ts +513 -0
  118. package/dist/routers/fees.d.ts.map +1 -0
  119. package/dist/routers/fees.js +88 -0
  120. package/dist/routers/fees.js.map +1 -0
  121. package/dist/routers/file.d.ts +2 -0
  122. package/dist/routers/file.d.ts.map +1 -1
  123. package/dist/routers/gradebook.d.ts +4 -1
  124. package/dist/routers/gradebook.d.ts.map +1 -1
  125. package/dist/routers/marketplace.d.ts +371 -0
  126. package/dist/routers/marketplace.d.ts.map +1 -0
  127. package/dist/routers/marketplace.js +87 -0
  128. package/dist/routers/marketplace.js.map +1 -0
  129. package/dist/routers/moduleItem.d.ts +263 -0
  130. package/dist/routers/moduleItem.d.ts.map +1 -0
  131. package/dist/routers/moduleItem.js +60 -0
  132. package/dist/routers/moduleItem.js.map +1 -0
  133. package/dist/routers/parent.d.ts +169 -0
  134. package/dist/routers/parent.d.ts.map +1 -0
  135. package/dist/routers/parent.js +49 -0
  136. package/dist/routers/parent.js.map +1 -0
  137. package/dist/routers/school.d.ts +476 -6
  138. package/dist/routers/school.d.ts.map +1 -1
  139. package/dist/routers/school.js +130 -4
  140. package/dist/routers/school.js.map +1 -1
  141. package/dist/routers/worksheet.d.ts +27 -0
  142. package/dist/routers/worksheet.d.ts.map +1 -1
  143. package/dist/routers/worksheet.js +10 -3
  144. package/dist/routers/worksheet.js.map +1 -1
  145. package/dist/services/academicCalendar.d.ts +173 -0
  146. package/dist/services/academicCalendar.d.ts.map +1 -0
  147. package/dist/services/academicCalendar.js +214 -0
  148. package/dist/services/academicCalendar.js.map +1 -0
  149. package/dist/services/adminInstitution.d.ts +2 -0
  150. package/dist/services/adminInstitution.d.ts.map +1 -1
  151. package/dist/services/agenda.d.ts +8 -0
  152. package/dist/services/agenda.d.ts.map +1 -1
  153. package/dist/services/assignment.d.ts +75 -0
  154. package/dist/services/assignment.d.ts.map +1 -1
  155. package/dist/services/assignment.js +91 -12
  156. package/dist/services/assignment.js.map +1 -1
  157. package/dist/services/attendance.d.ts +323 -10
  158. package/dist/services/attendance.d.ts.map +1 -1
  159. package/dist/services/attendance.js +385 -25
  160. package/dist/services/attendance.js.map +1 -1
  161. package/dist/services/attendanceMerge.d.ts +53 -0
  162. package/dist/services/attendanceMerge.d.ts.map +1 -0
  163. package/dist/services/attendanceMerge.js +87 -0
  164. package/dist/services/attendanceMerge.js.map +1 -0
  165. package/dist/services/attendanceRate.d.ts +24 -0
  166. package/dist/services/attendanceRate.d.ts.map +1 -0
  167. package/dist/services/attendanceRate.js +31 -0
  168. package/dist/services/attendanceRate.js.map +1 -0
  169. package/dist/services/attendanceStats.d.ts +22 -0
  170. package/dist/services/attendanceStats.d.ts.map +1 -0
  171. package/dist/services/attendanceStats.js +45 -0
  172. package/dist/services/attendanceStats.js.map +1 -0
  173. package/dist/services/class.d.ts +27 -0
  174. package/dist/services/class.d.ts.map +1 -1
  175. package/dist/services/class.js +26 -3
  176. package/dist/services/class.js.map +1 -1
  177. package/dist/services/event.d.ts +8 -0
  178. package/dist/services/event.d.ts.map +1 -1
  179. package/dist/services/fees.d.ts +422 -0
  180. package/dist/services/fees.d.ts.map +1 -0
  181. package/dist/services/fees.js +310 -0
  182. package/dist/services/fees.js.map +1 -0
  183. package/dist/services/feesAuthz.d.ts +49 -0
  184. package/dist/services/feesAuthz.d.ts.map +1 -0
  185. package/dist/services/feesAuthz.js +52 -0
  186. package/dist/services/feesAuthz.js.map +1 -0
  187. package/dist/services/file.d.ts +4 -0
  188. package/dist/services/file.d.ts.map +1 -1
  189. package/dist/services/gradebook.d.ts +18 -1
  190. package/dist/services/gradebook.d.ts.map +1 -1
  191. package/dist/services/gradebook.js +57 -2
  192. package/dist/services/gradebook.js.map +1 -1
  193. package/dist/services/idCard.d.ts +11 -0
  194. package/dist/services/idCard.d.ts.map +1 -0
  195. package/dist/services/idCard.js +122 -0
  196. package/dist/services/idCard.js.map +1 -0
  197. package/dist/services/marketplace.d.ts +301 -0
  198. package/dist/services/marketplace.d.ts.map +1 -0
  199. package/dist/services/marketplace.js +628 -0
  200. package/dist/services/marketplace.js.map +1 -0
  201. package/dist/services/moduleItem.d.ts +196 -0
  202. package/dist/services/moduleItem.d.ts.map +1 -0
  203. package/dist/services/moduleItem.js +188 -0
  204. package/dist/services/moduleItem.js.map +1 -0
  205. package/dist/services/notification.d.ts +58 -0
  206. package/dist/services/notification.d.ts.map +1 -1
  207. package/dist/services/notification.js +30 -2
  208. package/dist/services/notification.js.map +1 -1
  209. package/dist/services/parentPortal.d.ts +133 -0
  210. package/dist/services/parentPortal.d.ts.map +1 -0
  211. package/dist/services/parentPortal.js +328 -0
  212. package/dist/services/parentPortal.js.map +1 -0
  213. package/dist/services/school.d.ts +122 -3
  214. package/dist/services/school.d.ts.map +1 -1
  215. package/dist/services/school.js +179 -47
  216. package/dist/services/school.js.map +1 -1
  217. package/dist/services/studentImport.d.ts +34 -0
  218. package/dist/services/studentImport.d.ts.map +1 -0
  219. package/dist/services/studentImport.js +236 -0
  220. package/dist/services/studentImport.js.map +1 -0
  221. package/dist/services/transcript.d.ts +61 -0
  222. package/dist/services/transcript.d.ts.map +1 -0
  223. package/dist/services/transcript.js +243 -0
  224. package/dist/services/transcript.js.map +1 -0
  225. package/dist/services/worksheet.d.ts +22 -0
  226. package/dist/services/worksheet.d.ts.map +1 -1
  227. package/dist/services/worksheet.js +94 -2
  228. package/dist/services/worksheet.js.map +1 -1
  229. package/dist/trpc.d.ts +1 -1
  230. package/dist/trpc.d.ts.map +1 -1
  231. package/dist/trpc.js +5 -5
  232. package/dist/trpc.js.map +1 -1
  233. package/dist/utils/adminPanelAccess.d.ts +4 -5
  234. package/dist/utils/adminPanelAccess.d.ts.map +1 -1
  235. package/dist/utils/adminPanelAccess.js +12 -34
  236. package/dist/utils/adminPanelAccess.js.map +1 -1
  237. package/package.json +2 -1
  238. package/prisma/migrations/20260804120000_add_attendance_records_and_excusals/migration.sql +114 -0
  239. package/prisma/migrations/20260804120100_add_fee_plans_invoices_payments/migration.sql +101 -0
  240. package/prisma/migrations/20260804120200_add_marketplace_listings/migration.sql +54 -0
  241. package/prisma/migrations/20260804120300_content_platform_b3_b9/migration.sql +63 -0
  242. package/prisma/migrations/20260804120400_c_academic_ops/migration.sql +77 -0
  243. package/prisma/schema.prisma +371 -11
  244. package/scripts/import-nepal-cdc-curriculum.ts +379 -0
  245. package/src/lib/assignmentTypeDefaults.ts +222 -0
  246. package/src/lib/config/env.ts +0 -3
  247. package/src/lib/feePdf.ts +199 -0
  248. package/src/lib/feePdfFormat.ts +14 -0
  249. package/src/lib/idCardPdf.ts +336 -0
  250. package/src/lib/pdfLetterhead.ts +124 -0
  251. package/src/lib/transcriptPdf.ts +502 -0
  252. package/src/models/academicCalendar.ts +139 -0
  253. package/src/models/attendance.ts +704 -73
  254. package/src/models/class.ts +1 -0
  255. package/src/models/fees.ts +349 -0
  256. package/src/models/marketplace.ts +158 -0
  257. package/src/models/moduleItem.ts +107 -0
  258. package/src/models/school.ts +95 -0
  259. package/src/models/worksheet.ts +2 -0
  260. package/src/routers/_app.ts +10 -0
  261. package/src/routers/academicCalendar.ts +103 -0
  262. package/src/routers/assignment.ts +8 -0
  263. package/src/routers/attendance.ts +7 -3
  264. package/src/routers/class.ts +4 -0
  265. package/src/routers/fees.ts +126 -0
  266. package/src/routers/marketplace.ts +134 -0
  267. package/src/routers/moduleItem.ts +89 -0
  268. package/src/routers/parent.ts +68 -0
  269. package/src/routers/school.ts +184 -1
  270. package/src/routers/worksheet.ts +11 -0
  271. package/src/services/academicCalendar.ts +299 -0
  272. package/src/services/assignment.ts +122 -12
  273. package/src/services/attendance.ts +548 -26
  274. package/src/services/attendanceMerge.ts +139 -0
  275. package/src/services/attendanceRate.ts +41 -0
  276. package/src/services/attendanceStats.ts +63 -0
  277. package/src/services/class.ts +25 -1
  278. package/src/services/fees.ts +424 -0
  279. package/src/services/feesAuthz.ts +77 -0
  280. package/src/services/gradebook.ts +70 -0
  281. package/src/services/idCard.ts +147 -0
  282. package/src/services/marketplace.ts +785 -0
  283. package/src/services/moduleItem.ts +274 -0
  284. package/src/services/notification.ts +43 -0
  285. package/src/services/parentPortal.ts +380 -0
  286. package/src/services/school.ts +243 -45
  287. package/src/services/studentImport.ts +273 -0
  288. package/src/services/transcript.ts +313 -0
  289. package/src/services/worksheet.ts +111 -0
  290. package/src/trpc.ts +3 -3
  291. package/src/utils/adminPanelAccess.ts +12 -35
  292. package/tests/unit/assignmentTypeDefaults.test.ts +52 -0
  293. package/tests/unit/lib/feePdf.test.ts +15 -0
  294. package/tests/unit/lib/idCardPdf.test.ts +40 -0
  295. package/tests/unit/lib/transcriptPdf.test.ts +111 -0
  296. package/tests/unit/services/attendanceMerge.test.ts +90 -0
  297. package/tests/unit/services/attendanceRate.test.ts +38 -0
  298. package/tests/unit/services/attendanceStats.test.ts +54 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transcript.d.ts","sourceRoot":"/","sources":["services/transcript.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAKH,OAAO,EAIL,KAAK,uBAAuB,EAC7B,MAAM,yBAAyB,CAAC;AAUjC,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,IAAI,CAAC;IACZ,EAAE,CAAC,EAAE,IAAI,CAAC;IACV;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAkID,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,uBAAuB;;;;;;IAoF5B,yDAAyD;;;;GAK5D;AAED,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE;IACL,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,IAAI,CAAC,EAAE,IAAI,CAAC;IACZ,EAAE,CAAC,EAAE,IAAI,CAAC;IACV,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;;;;;;;QAfC,yDAAyD;;;;;;mBA6BhC,MAAM;iBAAW,MAAM;;GAwBnD"}
@@ -0,0 +1,243 @@
1
+ /**
2
+ * School-admin branded transcripts / progress reports (A4 — Nepal pilot).
3
+ *
4
+ * Aggregates per-class gradebook grades + grading boundaries + attendance %
5
+ * into a pdf-lib PDF. Branding: school name + logo (letterhead optional when
6
+ * present on School; schema does not include it yet — do not block).
7
+ *
8
+ * AcademicTerm (C1) is not modelled yet — accept `from`/`to` date range and/or
9
+ * a free-text `schoolYear` string for the report header.
10
+ *
11
+ * Attendance is read via the existing present/late/absent M2M lists only
12
+ * (same shaping as getStudentTimeline). Do not conflict with A1 Attendance
13
+ * refactor — rate math lives in attendanceRate.ts.
14
+ */
15
+
16
+ !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]="c7eb65fe-e18f-5b5b-9897-ce16dc61c93d")}catch(e){}}();
17
+ import { TRPCError } from "@trpc/server";
18
+ import { prisma } from "../lib/prisma.js";
19
+ import { bucket } from "../lib/googleCloudStorage.js";
20
+ import { buildTranscriptPdf, isLevelBasedBoundary, } from "../lib/transcriptPdf.js";
21
+ import { findActiveSchoolAdminMembership, getSchoolById, } from "../models/school.js";
22
+ import { computeAttendanceRate } from "./attendanceRate.js";
23
+ import { computeStudentGradeInClass } from "./gradebook.js";
24
+ const MAX_BULK = 50;
25
+ async function requireSchoolAdmin(schoolId, userId) {
26
+ const membership = await findActiveSchoolAdminMembership(schoolId, userId);
27
+ if (!membership) {
28
+ throw new TRPCError({
29
+ code: "FORBIDDEN",
30
+ message: "Only school admins can generate transcripts",
31
+ });
32
+ }
33
+ return membership;
34
+ }
35
+ async function loadFileBytes(path) {
36
+ if (!path)
37
+ return null;
38
+ try {
39
+ const [bytes] = await bucket.file(path).download();
40
+ return new Uint8Array(bytes);
41
+ }
42
+ catch {
43
+ return null;
44
+ }
45
+ }
46
+ function formatPeriodLabel(from, to) {
47
+ if (!from && !to)
48
+ return null;
49
+ const fmt = (d) => d.toISOString().slice(0, 10);
50
+ if (from && to)
51
+ return `${fmt(from)} – ${fmt(to)}`;
52
+ if (from)
53
+ return `From ${fmt(from)}`;
54
+ return `Through ${fmt(to)}`;
55
+ }
56
+ function slugify(value) {
57
+ return value
58
+ .toLowerCase()
59
+ .replace(/[^a-z0-9]+/g, "-")
60
+ .replace(/^-|-$/g, "")
61
+ .slice(0, 48);
62
+ }
63
+ async function fetchStudentAttendance(studentId, from, to) {
64
+ const range = {
65
+ ...(from ? { gte: from } : {}),
66
+ ...(to ? { lte: to } : {}),
67
+ };
68
+ const hasRange = Object.keys(range).length > 0;
69
+ // Same M2M present/late/absent read path as getStudentTimeline (A1-safe).
70
+ const attendance = await prisma.attendance.findMany({
71
+ where: {
72
+ OR: [
73
+ { present: { some: { id: studentId } } },
74
+ { late: { some: { id: studentId } } },
75
+ { absent: { some: { id: studentId } } },
76
+ ],
77
+ ...(hasRange ? { date: range } : {}),
78
+ },
79
+ select: {
80
+ id: true,
81
+ present: { where: { id: studentId }, select: { id: true } },
82
+ late: { where: { id: studentId }, select: { id: true } },
83
+ absent: { where: { id: studentId }, select: { id: true } },
84
+ },
85
+ });
86
+ return attendance.map((record) => ({
87
+ status: record.present.length
88
+ ? "PRESENT"
89
+ : record.late.length
90
+ ? "LATE"
91
+ : "ABSENT",
92
+ }));
93
+ }
94
+ async function buildClassRows(schoolId, studentId) {
95
+ const classes = await prisma.class.findMany({
96
+ where: {
97
+ schoolId,
98
+ students: { some: { id: studentId } },
99
+ },
100
+ select: {
101
+ id: true,
102
+ name: true,
103
+ subject: true,
104
+ section: true,
105
+ },
106
+ orderBy: [{ subject: "asc" }, { name: "asc" }],
107
+ });
108
+ const rows = [];
109
+ let levelVotes = 0;
110
+ let letterVotes = 0;
111
+ for (const cls of classes) {
112
+ const grade = await computeStudentGradeInClass(cls.id, studentId);
113
+ if (!grade)
114
+ continue;
115
+ if (isLevelBasedBoundary(grade.gradingBoundary?.structured)) {
116
+ levelVotes += 1;
117
+ }
118
+ else if (grade.gradingBoundary) {
119
+ letterVotes += 1;
120
+ }
121
+ rows.push({
122
+ className: cls.name,
123
+ subject: cls.subject,
124
+ section: cls.section,
125
+ percentage: grade.result.percentage,
126
+ gradeLabel: grade.letterGrade,
127
+ categorySummaries: grade.result.categories.map((c) => ({
128
+ name: c.name,
129
+ average: c.average,
130
+ })),
131
+ });
132
+ }
133
+ const variant = levelVotes > 0 && levelVotes >= letterVotes ? "level" : "letter";
134
+ return { rows, variant };
135
+ }
136
+ export async function generateTranscript(userId, input) {
137
+ await requireSchoolAdmin(input.schoolId, userId);
138
+ const school = await getSchoolById(input.schoolId);
139
+ if (!school) {
140
+ throw new TRPCError({ code: "NOT_FOUND", message: "School not found" });
141
+ }
142
+ const membership = await prisma.schoolMembership.findFirst({
143
+ where: {
144
+ schoolId: input.schoolId,
145
+ userId: input.studentId,
146
+ status: "ACTIVE",
147
+ role: "STUDENT",
148
+ },
149
+ select: {
150
+ user: {
151
+ select: {
152
+ id: true,
153
+ username: true,
154
+ profile: { select: { displayName: true } },
155
+ },
156
+ },
157
+ },
158
+ });
159
+ if (!membership) {
160
+ throw new TRPCError({
161
+ code: "NOT_FOUND",
162
+ message: "Student is not an active student in this school",
163
+ });
164
+ }
165
+ const studentName = membership.user.profile?.displayName || membership.user.username;
166
+ const [{ rows, variant }, attendanceStatuses, logoBytes] = await Promise.all([
167
+ buildClassRows(input.schoolId, input.studentId),
168
+ fetchStudentAttendance(input.studentId, input.from, input.to),
169
+ loadFileBytes(school.logo?.path),
170
+ ]);
171
+ // Soft letterhead support when schema later adds it — never required.
172
+ const schoolAny = school;
173
+ const letterheadPath = schoolAny.letterhead?.path ?? schoolAny.letterheadPath ?? null;
174
+ const letterheadType = schoolAny.letterhead?.type ?? schoolAny.letterheadType ?? null;
175
+ const letterheadBytes = await loadFileBytes(letterheadPath);
176
+ const attendance = computeAttendanceRate(attendanceStatuses);
177
+ const generatedAt = new Date();
178
+ const pdfBytes = await buildTranscriptPdf({
179
+ schoolName: school.name,
180
+ studentName,
181
+ studentUsername: membership.user.username,
182
+ schoolYear: input.schoolYear ?? null,
183
+ periodLabel: formatPeriodLabel(input.from, input.to),
184
+ variant,
185
+ classes: rows,
186
+ attendance,
187
+ logoBytes,
188
+ logoContentType: school.logo?.type ?? null,
189
+ letterheadBytes,
190
+ letterheadContentType: letterheadType,
191
+ generatedAt,
192
+ });
193
+ const yearSlug = input.schoolYear
194
+ ? slugify(input.schoolYear)
195
+ : generatedAt.toISOString().slice(0, 10);
196
+ const fileName = `progress-report-${slugify(studentName) || membership.user.username}-${yearSlug}.pdf`;
197
+ return {
198
+ pdfBase64: Buffer.from(pdfBytes).toString("base64"),
199
+ fileName,
200
+ variant,
201
+ studentId: input.studentId,
202
+ studentName,
203
+ /** C1 blocker: AcademicTerm model does not exist yet. */
204
+ academicTermSupported: false,
205
+ attendance,
206
+ classCount: rows.length,
207
+ };
208
+ }
209
+ export async function generateTranscripts(userId, input) {
210
+ await requireSchoolAdmin(input.schoolId, userId);
211
+ const uniqueIds = [...new Set(input.studentIds)].slice(0, MAX_BULK);
212
+ if (uniqueIds.length === 0) {
213
+ throw new TRPCError({
214
+ code: "BAD_REQUEST",
215
+ message: "Select at least one student",
216
+ });
217
+ }
218
+ const results = [];
219
+ const errors = [];
220
+ for (const studentId of uniqueIds) {
221
+ try {
222
+ const result = await generateTranscript(userId, {
223
+ schoolId: input.schoolId,
224
+ studentId,
225
+ from: input.from,
226
+ to: input.to,
227
+ schoolYear: input.schoolYear,
228
+ });
229
+ results.push(result);
230
+ }
231
+ catch (error) {
232
+ const message = error instanceof TRPCError
233
+ ? error.message
234
+ : error instanceof Error
235
+ ? error.message
236
+ : "Failed to generate transcript";
237
+ errors.push({ studentId, message });
238
+ }
239
+ }
240
+ return { transcripts: results, errors };
241
+ }
242
+ //# sourceMappingURL=transcript.js.map
243
+ //# debugId=c7eb65fe-e18f-5b5b-9897-ce16dc61c93d
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transcript.js","sources":["services/transcript.ts"],"sourceRoot":"/","sourcesContent":["/**\n * School-admin branded transcripts / progress reports (A4 — Nepal pilot).\n *\n * Aggregates per-class gradebook grades + grading boundaries + attendance %\n * into a pdf-lib PDF. Branding: school name + logo (letterhead optional when\n * present on School; schema does not include it yet — do not block).\n *\n * AcademicTerm (C1) is not modelled yet — accept `from`/`to` date range and/or\n * a free-text `schoolYear` string for the report header.\n *\n * Attendance is read via the existing present/late/absent M2M lists only\n * (same shaping as getStudentTimeline). Do not conflict with A1 Attendance\n * refactor — rate math lives in attendanceRate.ts.\n */\n\nimport { TRPCError } from \"@trpc/server\";\nimport { prisma } from \"../lib/prisma.js\";\nimport { bucket } from \"../lib/googleCloudStorage.js\";\nimport {\n buildTranscriptPdf,\n isLevelBasedBoundary,\n type TranscriptClassRow,\n type TranscriptReportVariant,\n} from \"../lib/transcriptPdf.js\";\nimport {\n findActiveSchoolAdminMembership,\n getSchoolById,\n} from \"../models/school.js\";\nimport { computeAttendanceRate } from \"./attendanceRate.js\";\nimport { computeStudentGradeInClass } from \"./gradebook.js\";\n\nconst MAX_BULK = 50;\n\nexport interface GenerateTranscriptInput {\n schoolId: string;\n studentId: string;\n from?: Date;\n to?: Date;\n /**\n * Free-text school year label (e.g. \"2082 BS\", \"2025–2026\").\n * Prefer AcademicTerm (C1) once available.\n */\n schoolYear?: string;\n}\n\nasync function requireSchoolAdmin(schoolId: string, userId: string) {\n const membership = await findActiveSchoolAdminMembership(schoolId, userId);\n if (!membership) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Only school admins can generate transcripts\",\n });\n }\n return membership;\n}\n\nasync function loadFileBytes(\n path: string | null | undefined\n): Promise<Uint8Array | null> {\n if (!path) return null;\n try {\n const [bytes] = await bucket.file(path).download();\n return new Uint8Array(bytes);\n } catch {\n return null;\n }\n}\n\nfunction formatPeriodLabel(from?: Date, to?: Date): string | null {\n if (!from && !to) return null;\n const fmt = (d: Date) => d.toISOString().slice(0, 10);\n if (from && to) return `${fmt(from)} – ${fmt(to)}`;\n if (from) return `From ${fmt(from)}`;\n return `Through ${fmt(to!)}`;\n}\n\nfunction slugify(value: string): string {\n return value\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-|-$/g, \"\")\n .slice(0, 48);\n}\n\nasync function fetchStudentAttendance(\n studentId: string,\n from?: Date,\n to?: Date\n) {\n const range = {\n ...(from ? { gte: from } : {}),\n ...(to ? { lte: to } : {}),\n };\n const hasRange = Object.keys(range).length > 0;\n\n // Same M2M present/late/absent read path as getStudentTimeline (A1-safe).\n const attendance = await prisma.attendance.findMany({\n where: {\n OR: [\n { present: { some: { id: studentId } } },\n { late: { some: { id: studentId } } },\n { absent: { some: { id: studentId } } },\n ],\n ...(hasRange ? { date: range } : {}),\n },\n select: {\n id: true,\n present: { where: { id: studentId }, select: { id: true } },\n late: { where: { id: studentId }, select: { id: true } },\n absent: { where: { id: studentId }, select: { id: true } },\n },\n });\n\n return attendance.map((record) => ({\n status: record.present.length\n ? (\"PRESENT\" as const)\n : record.late.length\n ? (\"LATE\" as const)\n : (\"ABSENT\" as const),\n }));\n}\n\nasync function buildClassRows(\n schoolId: string,\n studentId: string\n): Promise<{ rows: TranscriptClassRow[]; variant: TranscriptReportVariant }> {\n const classes = await prisma.class.findMany({\n where: {\n schoolId,\n students: { some: { id: studentId } },\n },\n select: {\n id: true,\n name: true,\n subject: true,\n section: true,\n },\n orderBy: [{ subject: \"asc\" }, { name: \"asc\" }],\n });\n\n const rows: TranscriptClassRow[] = [];\n let levelVotes = 0;\n let letterVotes = 0;\n\n for (const cls of classes) {\n const grade = await computeStudentGradeInClass(cls.id, studentId);\n if (!grade) continue;\n\n if (isLevelBasedBoundary(grade.gradingBoundary?.structured)) {\n levelVotes += 1;\n } else if (grade.gradingBoundary) {\n letterVotes += 1;\n }\n\n rows.push({\n className: cls.name,\n subject: cls.subject,\n section: cls.section,\n percentage: grade.result.percentage,\n gradeLabel: grade.letterGrade,\n categorySummaries: grade.result.categories.map((c) => ({\n name: c.name,\n average: c.average,\n })),\n });\n }\n\n const variant: TranscriptReportVariant =\n levelVotes > 0 && levelVotes >= letterVotes ? \"level\" : \"letter\";\n\n return { rows, variant };\n}\n\nexport async function generateTranscript(\n userId: string,\n input: GenerateTranscriptInput\n) {\n await requireSchoolAdmin(input.schoolId, userId);\n\n const school = await getSchoolById(input.schoolId);\n if (!school) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"School not found\" });\n }\n\n const membership = await prisma.schoolMembership.findFirst({\n where: {\n schoolId: input.schoolId,\n userId: input.studentId,\n status: \"ACTIVE\",\n role: \"STUDENT\",\n },\n select: {\n user: {\n select: {\n id: true,\n username: true,\n profile: { select: { displayName: true } },\n },\n },\n },\n });\n if (!membership) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Student is not an active student in this school\",\n });\n }\n\n const studentName =\n membership.user.profile?.displayName || membership.user.username;\n\n const [{ rows, variant }, attendanceStatuses, logoBytes] = await Promise.all([\n buildClassRows(input.schoolId, input.studentId),\n fetchStudentAttendance(input.studentId, input.from, input.to),\n loadFileBytes(school.logo?.path),\n ]);\n\n // Soft letterhead support when schema later adds it — never required.\n const schoolAny = school as {\n letterhead?: { path?: string | null; type?: string | null } | null;\n letterheadPath?: string | null;\n letterheadType?: string | null;\n };\n const letterheadPath =\n schoolAny.letterhead?.path ?? schoolAny.letterheadPath ?? null;\n const letterheadType =\n schoolAny.letterhead?.type ?? schoolAny.letterheadType ?? null;\n const letterheadBytes = await loadFileBytes(letterheadPath);\n\n const attendance = computeAttendanceRate(attendanceStatuses);\n const generatedAt = new Date();\n\n const pdfBytes = await buildTranscriptPdf({\n schoolName: school.name,\n studentName,\n studentUsername: membership.user.username,\n schoolYear: input.schoolYear ?? null,\n periodLabel: formatPeriodLabel(input.from, input.to),\n variant,\n classes: rows,\n attendance,\n logoBytes,\n logoContentType: school.logo?.type ?? null,\n letterheadBytes,\n letterheadContentType: letterheadType,\n generatedAt,\n });\n\n const yearSlug = input.schoolYear\n ? slugify(input.schoolYear)\n : generatedAt.toISOString().slice(0, 10);\n const fileName = `progress-report-${slugify(studentName) || membership.user.username}-${yearSlug}.pdf`;\n\n return {\n pdfBase64: Buffer.from(pdfBytes).toString(\"base64\"),\n fileName,\n variant,\n studentId: input.studentId,\n studentName,\n /** C1 blocker: AcademicTerm model does not exist yet. */\n academicTermSupported: false as const,\n attendance,\n classCount: rows.length,\n };\n}\n\nexport async function generateTranscripts(\n userId: string,\n input: {\n schoolId: string;\n studentIds: string[];\n from?: Date;\n to?: Date;\n schoolYear?: string;\n }\n) {\n await requireSchoolAdmin(input.schoolId, userId);\n\n const uniqueIds = [...new Set(input.studentIds)].slice(0, MAX_BULK);\n if (uniqueIds.length === 0) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Select at least one student\",\n });\n }\n\n type TranscriptResult = Awaited<ReturnType<typeof generateTranscript>>;\n const results: TranscriptResult[] = [];\n const errors: { studentId: string; message: string }[] = [];\n\n for (const studentId of uniqueIds) {\n try {\n const result = await generateTranscript(userId, {\n schoolId: input.schoolId,\n studentId,\n from: input.from,\n to: input.to,\n schoolYear: input.schoolYear,\n });\n results.push(result);\n } catch (error) {\n const message =\n error instanceof TRPCError\n ? error.message\n : error instanceof Error\n ? error.message\n : \"Failed to generate transcript\";\n errors.push({ studentId, message });\n }\n }\n\n return { transcripts: results, errors };\n}\n"],"names":[],"mappings":"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","debug_id":"c7eb65fe-e18f-5b5b-9897-ce16dc61c93d"}
@@ -14,6 +14,7 @@ export declare function getWorksheet(worksheetId: string, userId: string): Promi
14
14
  discussionsEnabled: boolean;
15
15
  studentsCanCreateDiscussions: boolean;
16
16
  studentsCanReply: boolean;
17
+ sidebarFeatures: Prisma.JsonValue | null;
17
18
  treatMissingAsZero: boolean;
18
19
  gradeRoundingMode: import(".prisma/client").$Enums.GradeRoundingMode;
19
20
  } | null;
@@ -35,6 +36,7 @@ export declare function getWorksheet(worksheetId: string, userId: string): Promi
35
36
  worksheetId: string;
36
37
  order: number | null;
37
38
  answer: string;
39
+ parentQuestionId: string | null;
38
40
  }[];
39
41
  } & {
40
42
  status: import(".prisma/client").$Enums.JobStatus | null;
@@ -159,6 +161,7 @@ export declare function addQuestionToWorksheet(worksheetId: string, userId: stri
159
161
  markScheme?: object;
160
162
  type: WorksheetQuestionType;
161
163
  order?: number;
164
+ parentQuestionId?: string | null;
162
165
  }): Promise<{
163
166
  type: import(".prisma/client").$Enums.WorksheetQuestionType;
164
167
  options: Prisma.JsonValue | null;
@@ -171,6 +174,7 @@ export declare function addQuestionToWorksheet(worksheetId: string, userId: stri
171
174
  worksheetId: string;
172
175
  order: number | null;
173
176
  answer: string;
177
+ parentQuestionId: string | null;
174
178
  }>;
175
179
  /** A question's full editable state as captured before an AI modifier run. */
176
180
  export interface WorksheetQuestionSnapshot {
@@ -199,6 +203,7 @@ export declare function updateWorksheetQuestionRecord(worksheetId: string, quest
199
203
  options?: object;
200
204
  markScheme?: object;
201
205
  type?: WorksheetQuestionType;
206
+ parentQuestionId?: string | null;
202
207
  }): Promise<{
203
208
  type: import(".prisma/client").$Enums.WorksheetQuestionType;
204
209
  options: Prisma.JsonValue | null;
@@ -211,7 +216,23 @@ export declare function updateWorksheetQuestionRecord(worksheetId: string, quest
211
216
  worksheetId: string;
212
217
  order: number | null;
213
218
  answer: string;
219
+ parentQuestionId: string | null;
214
220
  }>;
221
+ /** Answering statistics for each question — shown in the teacher worksheet editor. */
222
+ export declare function getWorksheetQuestionStats(worksheetId: string, userId: string): Promise<{
223
+ questionId: string;
224
+ parentQuestionId: string | null;
225
+ order: number | null;
226
+ type: import(".prisma/client").$Enums.WorksheetQuestionType;
227
+ points: number;
228
+ promptPreview: string;
229
+ totalResponses: number;
230
+ answeredCount: number;
231
+ submittedCount: number;
232
+ correctCount: number;
233
+ averagePoints: number | null;
234
+ correctRate: number | null;
235
+ }[]>;
215
236
  export declare function deleteWorksheetQuestionRecord(worksheetId: string, questionId: string, userId: string): Promise<{
216
237
  type: import(".prisma/client").$Enums.WorksheetQuestionType;
217
238
  options: Prisma.JsonValue | null;
@@ -224,6 +245,7 @@ export declare function deleteWorksheetQuestionRecord(worksheetId: string, quest
224
245
  worksheetId: string;
225
246
  order: number | null;
226
247
  answer: string;
248
+ parentQuestionId: string | null;
227
249
  }>;
228
250
  export declare function getWorksheetSubmission(worksheetId: string, submissionId: string, userId: string): Promise<{
229
251
  responses: ({
@@ -1 +1 @@
1
- {"version":3,"file":"worksheet.d.ts","sourceRoot":"/","sources":["services/worksheet.ts"],"names":[],"mappings":"AAKA,OAAO,EAAa,MAAM,EAAE,KAAK,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AA8D/E,qDAAqD;AACrD,wBAAsB,YAAY,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAerE;AAED,iCAAiC;AACjC,wBAAsB,eAAe,CAAC,EAAE,EAAE,MAAM,oBAG/C;AAED,wBAAsB,cAAc,CAAC,OAAO,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;KAMnD;AAED,wBAAsB,qBAAqB,CACzC,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE;;;;;;;;;GAaxB;AAED,wBAAsB,qBAAqB,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;;;;;;;;;GAS9E;AAED,wBAAsB,qBAAqB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM;;;;;;;;;GAKxE;AAED;;;GAGG;AACH,wBAAsB,wBAAwB,CAC5C,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM;;;;;;;;;GAmCf;AAED;;;;;;;;GAQG;AACH,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,MAAM,EACX,MAAM,CAAC,EAAE,MAAM,EACf,OAAO,GAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAO;;;;;;;;;GA2DhC;AAED;;GAEG;AACH,wBAAsB,8BAA8B,CAClD,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,OAAO,GAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAO;;;;;;;;;GAoChC;AAED,wBAAsB,sBAAsB,CAC1C,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE;IACJ,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,qBAAqB,CAAC;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;;;;;;;;;;;;GAaF;AAED,8EAA8E;AAC9E,MAAM,WAAW,yBAAyB;IACxC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,qBAAqB,CAAC;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,OAAO,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAOD;;;;GAIG;AACH,wBAAsB,yBAAyB,CAC7C,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,yBAAyB,EAAE,iBAwCvC;AAED,wBAAsB,yBAAyB,CAC7C,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,QAAQ,GAAG,OAAO,EAC5B,QAAQ,EAAE,MAAM;;KAmDjB;AAED,wBAAsB,6BAA6B,CACjD,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE;IACJ,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,qBAAqB,CAAC;CAC9B;;;;;;;;;;;;GAUF;AAED,wBAAsB,6BAA6B,CACjD,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM;;;;;;;;;;;;GAUf;AAED,wBAAsB,sBAAsB,CAC1C,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2Bf;AAED,wBAAsB,uBAAuB,CAC3C,mBAAmB,EAAE,MAAM,EAC3B,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;GAmEf;AAED,wBAAsB,aAAa,CACjC,mBAAmB,EAAE,MAAM,EAC3B,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM;;;;;;;;;;;;;;GAcf;AAED,wBAAsB,eAAe,CACnC,mBAAmB,EAAE,MAAM,EAC3B,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM;;;;;;;;;;;;;;UAcf;AAED,wBAAsB,UAAU,CAC9B,mBAAmB,EAAE,MAAM,EAC3B,MAAM,EAAE,MAAM,iBAef;AAED,wBAAsB,WAAW,CAC/B,UAAU,EAAE,MAAM,EAClB,0BAA0B,EAAE,MAAM,EAClC,MAAM,EAAE,MAAM,EACd,IAAI,EAAE;IACJ,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;;;;;;;;;;;;;;GA0EF;AAED,wBAAsB,oBAAoB,CACxC,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM;;;;;;;;;;;;GAmBjB"}
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]="ca197d66-f140-5936-afe3-f5567f566d05")}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]="8196c4a5-acef-500c-8732-025782486e87")}catch(e){}}();
7
7
  import { TRPCError } from "@trpc/server";
8
8
  import { JobStatus, Prisma } from "@prisma/client";
9
9
  import { prisma } from "../lib/prisma.js";
@@ -221,6 +221,21 @@ export async function addQuestionToWorksheet(worksheetId, userId, data) {
221
221
  message: "Only class teachers can modify worksheet questions",
222
222
  });
223
223
  }
224
+ if (data.parentQuestionId) {
225
+ const parent = await findWorksheetQuestionByIdInWorksheet(data.parentQuestionId, worksheetId);
226
+ if (!parent) {
227
+ throw new TRPCError({
228
+ code: "BAD_REQUEST",
229
+ message: "Parent question not found in this worksheet",
230
+ });
231
+ }
232
+ if (parent.parentQuestionId) {
233
+ throw new TRPCError({
234
+ code: "BAD_REQUEST",
235
+ message: "Subquestions cannot themselves have children",
236
+ });
237
+ }
238
+ }
224
239
  return createWorksheetQuestion({
225
240
  worksheetId,
226
241
  ...data,
@@ -322,8 +337,85 @@ export async function updateWorksheetQuestionRecord(worksheetId, questionId, use
322
337
  message: "Only class teachers can update worksheet questions",
323
338
  });
324
339
  }
340
+ if (data.parentQuestionId) {
341
+ if (data.parentQuestionId === questionId) {
342
+ throw new TRPCError({
343
+ code: "BAD_REQUEST",
344
+ message: "A question cannot be its own parent",
345
+ });
346
+ }
347
+ const parent = await findWorksheetQuestionByIdInWorksheet(data.parentQuestionId, worksheetId);
348
+ if (!parent) {
349
+ throw new TRPCError({
350
+ code: "BAD_REQUEST",
351
+ message: "Parent question not found in this worksheet",
352
+ });
353
+ }
354
+ if (parent.parentQuestionId) {
355
+ throw new TRPCError({
356
+ code: "BAD_REQUEST",
357
+ message: "Subquestions cannot themselves have children",
358
+ });
359
+ }
360
+ }
325
361
  return updateWorksheetQuestion(questionId, data);
326
362
  }
363
+ /** Answering statistics for each question — shown in the teacher worksheet editor. */
364
+ export async function getWorksheetQuestionStats(worksheetId, userId) {
365
+ const worksheet = await findWorksheetByIdForTeacher(worksheetId, userId);
366
+ if (!worksheet) {
367
+ throw new TRPCError({
368
+ code: "FORBIDDEN",
369
+ message: "Only class teachers can view worksheet answering statistics",
370
+ });
371
+ }
372
+ const questions = await prisma.worksheetQuestion.findMany({
373
+ where: { worksheetId },
374
+ select: {
375
+ id: true,
376
+ parentQuestionId: true,
377
+ order: true,
378
+ points: true,
379
+ question: true,
380
+ type: true,
381
+ studentResponses: {
382
+ select: {
383
+ id: true,
384
+ isCorrect: true,
385
+ points: true,
386
+ response: true,
387
+ studentWorksheetResponse: {
388
+ select: { submitted: true },
389
+ },
390
+ },
391
+ },
392
+ },
393
+ orderBy: { order: "asc" },
394
+ });
395
+ return questions.map((q) => {
396
+ const responses = q.studentResponses;
397
+ const answered = responses.filter((r) => r.response !== null && r.response !== "");
398
+ const submitted = responses.filter((r) => r.studentWorksheetResponse?.submitted);
399
+ const correct = answered.filter((r) => r.isCorrect).length;
400
+ return {
401
+ questionId: q.id,
402
+ parentQuestionId: q.parentQuestionId,
403
+ order: q.order,
404
+ type: q.type,
405
+ points: q.points,
406
+ promptPreview: q.question.slice(0, 120),
407
+ totalResponses: responses.length,
408
+ answeredCount: answered.length,
409
+ submittedCount: submitted.length,
410
+ correctCount: correct,
411
+ averagePoints: answered.length === 0
412
+ ? null
413
+ : answered.reduce((sum, r) => sum + (r.points ?? 0), 0) /
414
+ answered.length,
415
+ correctRate: answered.length === 0 ? null : correct / answered.length,
416
+ };
417
+ });
418
+ }
327
419
  export async function deleteWorksheetQuestionRecord(worksheetId, questionId, userId) {
328
420
  const question = await findWorksheetQuestionByIdForTeacher(questionId, userId);
329
421
  if (!question || question.worksheetId !== worksheetId) {
@@ -504,4 +596,4 @@ export async function addCommentToResponse(responseId, comment, authorId) {
504
596
  });
505
597
  }
506
598
  //# sourceMappingURL=worksheet.js.map
507
- //# debugId=ca197d66-f140-5936-afe3-f5567f566d05
599
+ //# debugId=8196c4a5-acef-500c-8732-025782486e87