@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.
- package/.env.example +0 -1
- package/data/curriculum/nepal-cdc-g4-g5/README.md +90 -0
- package/data/curriculum/nepal-cdc-g4-g5/manifest.json +97 -0
- package/data/curriculum/nepal-cdc-g4-g5/proficiency-scale.json +76 -0
- package/data/curriculum/nepal-cdc-g4-g5/subjects/english.json +6936 -0
- package/data/curriculum/nepal-cdc-g4-g5/subjects/health-physical-creative-arts.json +3401 -0
- package/data/curriculum/nepal-cdc-g4-g5/subjects/mathematics.json +1511 -0
- package/data/curriculum/nepal-cdc-g4-g5/subjects/nepali.json +7928 -0
- package/data/curriculum/nepal-cdc-g4-g5/subjects/science-technology.json +2420 -0
- package/data/curriculum/nepal-cdc-g4-g5/subjects/social-studies.json +2177 -0
- package/dist/lib/assignmentTypeDefaults.d.ts +41 -0
- package/dist/lib/assignmentTypeDefaults.d.ts.map +1 -0
- package/dist/lib/assignmentTypeDefaults.js +167 -0
- package/dist/lib/assignmentTypeDefaults.js.map +1 -0
- package/dist/lib/config/env.d.ts +0 -8
- package/dist/lib/config/env.d.ts.map +1 -1
- package/dist/lib/config/env.js +2 -5
- package/dist/lib/config/env.js.map +1 -1
- package/dist/lib/feePdf.d.ts +19 -0
- package/dist/lib/feePdf.d.ts.map +1 -0
- package/dist/lib/feePdf.js +167 -0
- package/dist/lib/feePdf.js.map +1 -0
- package/dist/lib/feePdfFormat.d.ts +4 -0
- package/dist/lib/feePdfFormat.d.ts.map +1 -0
- package/dist/lib/feePdfFormat.js +16 -0
- package/dist/lib/feePdfFormat.js.map +1 -0
- package/dist/lib/idCardPdf.d.ts +26 -0
- package/dist/lib/idCardPdf.d.ts.map +1 -0
- package/dist/lib/idCardPdf.js +272 -0
- package/dist/lib/idCardPdf.js.map +1 -0
- package/dist/lib/pdfLetterhead.d.ts +41 -0
- package/dist/lib/pdfLetterhead.d.ts.map +1 -0
- package/dist/lib/pdfLetterhead.js +86 -0
- package/dist/lib/pdfLetterhead.js.map +1 -0
- package/dist/lib/transcriptPdf.d.ts +55 -0
- package/dist/lib/transcriptPdf.d.ts.map +1 -0
- package/dist/lib/transcriptPdf.js +303 -0
- package/dist/lib/transcriptPdf.js.map +1 -0
- package/dist/models/academicCalendar.d.ts +198 -0
- package/dist/models/academicCalendar.d.ts.map +1 -0
- package/dist/models/academicCalendar.js +106 -0
- package/dist/models/academicCalendar.js.map +1 -0
- package/dist/models/agenda.d.ts +8 -0
- package/dist/models/agenda.d.ts.map +1 -1
- package/dist/models/announcement.d.ts +2 -0
- package/dist/models/announcement.d.ts.map +1 -1
- package/dist/models/assignment.d.ts +66 -0
- package/dist/models/assignment.d.ts.map +1 -1
- package/dist/models/attendance.d.ts +659 -21
- package/dist/models/attendance.d.ts.map +1 -1
- package/dist/models/attendance.js +544 -75
- package/dist/models/attendance.js.map +1 -1
- package/dist/models/auth.d.ts +2 -0
- package/dist/models/auth.d.ts.map +1 -1
- package/dist/models/class.d.ts +12 -0
- package/dist/models/class.d.ts.map +1 -1
- package/dist/models/class.js +3 -2
- package/dist/models/class.js.map +1 -1
- package/dist/models/event.d.ts +16 -0
- package/dist/models/event.d.ts.map +1 -1
- package/dist/models/fees.d.ts +554 -0
- package/dist/models/fees.d.ts.map +1 -0
- package/dist/models/fees.js +278 -0
- package/dist/models/fees.js.map +1 -0
- package/dist/models/file.d.ts +29 -0
- package/dist/models/file.d.ts.map +1 -1
- package/dist/models/folder.d.ts +2 -0
- package/dist/models/folder.d.ts.map +1 -1
- package/dist/models/labChat.d.ts +2 -0
- package/dist/models/labChat.d.ts.map +1 -1
- package/dist/models/marketplace.d.ts +178 -0
- package/dist/models/marketplace.d.ts.map +1 -0
- package/dist/models/marketplace.js +115 -0
- package/dist/models/marketplace.js.map +1 -0
- package/dist/models/moduleItem.d.ts +304 -0
- package/dist/models/moduleItem.d.ts.map +1 -0
- package/dist/models/moduleItem.js +78 -0
- package/dist/models/moduleItem.js.map +1 -0
- package/dist/models/newtonChat.d.ts +1 -0
- package/dist/models/newtonChat.d.ts.map +1 -1
- package/dist/models/school.d.ts +193 -0
- package/dist/models/school.d.ts.map +1 -1
- package/dist/models/school.js +80 -2
- package/dist/models/school.js.map +1 -1
- package/dist/models/section.d.ts +4 -0
- package/dist/models/section.d.ts.map +1 -1
- package/dist/models/worksheet.d.ts +19 -0
- package/dist/models/worksheet.d.ts.map +1 -1
- package/dist/models/worksheet.js +2 -2
- package/dist/models/worksheet.js.map +1 -1
- package/dist/routers/_app.d.ts +11605 -7133
- package/dist/routers/_app.d.ts.map +1 -1
- package/dist/routers/_app.js +12 -2
- package/dist/routers/_app.js.map +1 -1
- package/dist/routers/academicCalendar.d.ts +229 -0
- package/dist/routers/academicCalendar.d.ts.map +1 -0
- package/dist/routers/academicCalendar.js +75 -0
- package/dist/routers/academicCalendar.js.map +1 -0
- package/dist/routers/admin.d.ts +2 -0
- package/dist/routers/admin.d.ts.map +1 -1
- package/dist/routers/agenda.d.ts +8 -0
- package/dist/routers/agenda.d.ts.map +1 -1
- package/dist/routers/assignment.d.ts +75 -0
- package/dist/routers/assignment.d.ts.map +1 -1
- package/dist/routers/assignment.js +10 -2
- package/dist/routers/assignment.js.map +1 -1
- package/dist/routers/attendance.d.ts +103 -0
- package/dist/routers/attendance.d.ts.map +1 -1
- package/dist/routers/attendance.js +8 -5
- package/dist/routers/attendance.js.map +1 -1
- package/dist/routers/class.d.ts +27 -0
- package/dist/routers/class.d.ts.map +1 -1
- package/dist/routers/class.js +6 -2
- package/dist/routers/class.js.map +1 -1
- package/dist/routers/event.d.ts +8 -0
- package/dist/routers/event.d.ts.map +1 -1
- package/dist/routers/fees.d.ts +513 -0
- package/dist/routers/fees.d.ts.map +1 -0
- package/dist/routers/fees.js +88 -0
- package/dist/routers/fees.js.map +1 -0
- package/dist/routers/file.d.ts +2 -0
- package/dist/routers/file.d.ts.map +1 -1
- package/dist/routers/gradebook.d.ts +4 -1
- package/dist/routers/gradebook.d.ts.map +1 -1
- package/dist/routers/marketplace.d.ts +371 -0
- package/dist/routers/marketplace.d.ts.map +1 -0
- package/dist/routers/marketplace.js +87 -0
- package/dist/routers/marketplace.js.map +1 -0
- package/dist/routers/moduleItem.d.ts +263 -0
- package/dist/routers/moduleItem.d.ts.map +1 -0
- package/dist/routers/moduleItem.js +60 -0
- package/dist/routers/moduleItem.js.map +1 -0
- package/dist/routers/parent.d.ts +169 -0
- package/dist/routers/parent.d.ts.map +1 -0
- package/dist/routers/parent.js +49 -0
- package/dist/routers/parent.js.map +1 -0
- package/dist/routers/school.d.ts +476 -6
- package/dist/routers/school.d.ts.map +1 -1
- package/dist/routers/school.js +130 -4
- package/dist/routers/school.js.map +1 -1
- package/dist/routers/worksheet.d.ts +27 -0
- package/dist/routers/worksheet.d.ts.map +1 -1
- package/dist/routers/worksheet.js +10 -3
- package/dist/routers/worksheet.js.map +1 -1
- package/dist/services/academicCalendar.d.ts +173 -0
- package/dist/services/academicCalendar.d.ts.map +1 -0
- package/dist/services/academicCalendar.js +214 -0
- package/dist/services/academicCalendar.js.map +1 -0
- package/dist/services/adminInstitution.d.ts +2 -0
- package/dist/services/adminInstitution.d.ts.map +1 -1
- package/dist/services/agenda.d.ts +8 -0
- package/dist/services/agenda.d.ts.map +1 -1
- package/dist/services/assignment.d.ts +75 -0
- package/dist/services/assignment.d.ts.map +1 -1
- package/dist/services/assignment.js +91 -12
- package/dist/services/assignment.js.map +1 -1
- package/dist/services/attendance.d.ts +323 -10
- package/dist/services/attendance.d.ts.map +1 -1
- package/dist/services/attendance.js +385 -25
- package/dist/services/attendance.js.map +1 -1
- package/dist/services/attendanceMerge.d.ts +53 -0
- package/dist/services/attendanceMerge.d.ts.map +1 -0
- package/dist/services/attendanceMerge.js +87 -0
- package/dist/services/attendanceMerge.js.map +1 -0
- package/dist/services/attendanceRate.d.ts +24 -0
- package/dist/services/attendanceRate.d.ts.map +1 -0
- package/dist/services/attendanceRate.js +31 -0
- package/dist/services/attendanceRate.js.map +1 -0
- package/dist/services/attendanceStats.d.ts +22 -0
- package/dist/services/attendanceStats.d.ts.map +1 -0
- package/dist/services/attendanceStats.js +45 -0
- package/dist/services/attendanceStats.js.map +1 -0
- package/dist/services/class.d.ts +27 -0
- package/dist/services/class.d.ts.map +1 -1
- package/dist/services/class.js +26 -3
- package/dist/services/class.js.map +1 -1
- package/dist/services/event.d.ts +8 -0
- package/dist/services/event.d.ts.map +1 -1
- package/dist/services/fees.d.ts +422 -0
- package/dist/services/fees.d.ts.map +1 -0
- package/dist/services/fees.js +310 -0
- package/dist/services/fees.js.map +1 -0
- package/dist/services/feesAuthz.d.ts +49 -0
- package/dist/services/feesAuthz.d.ts.map +1 -0
- package/dist/services/feesAuthz.js +52 -0
- package/dist/services/feesAuthz.js.map +1 -0
- package/dist/services/file.d.ts +4 -0
- package/dist/services/file.d.ts.map +1 -1
- package/dist/services/gradebook.d.ts +18 -1
- package/dist/services/gradebook.d.ts.map +1 -1
- package/dist/services/gradebook.js +57 -2
- package/dist/services/gradebook.js.map +1 -1
- package/dist/services/idCard.d.ts +11 -0
- package/dist/services/idCard.d.ts.map +1 -0
- package/dist/services/idCard.js +122 -0
- package/dist/services/idCard.js.map +1 -0
- package/dist/services/marketplace.d.ts +301 -0
- package/dist/services/marketplace.d.ts.map +1 -0
- package/dist/services/marketplace.js +628 -0
- package/dist/services/marketplace.js.map +1 -0
- package/dist/services/moduleItem.d.ts +196 -0
- package/dist/services/moduleItem.d.ts.map +1 -0
- package/dist/services/moduleItem.js +188 -0
- package/dist/services/moduleItem.js.map +1 -0
- package/dist/services/notification.d.ts +58 -0
- package/dist/services/notification.d.ts.map +1 -1
- package/dist/services/notification.js +30 -2
- package/dist/services/notification.js.map +1 -1
- package/dist/services/parentPortal.d.ts +133 -0
- package/dist/services/parentPortal.d.ts.map +1 -0
- package/dist/services/parentPortal.js +328 -0
- package/dist/services/parentPortal.js.map +1 -0
- package/dist/services/school.d.ts +122 -3
- package/dist/services/school.d.ts.map +1 -1
- package/dist/services/school.js +179 -47
- package/dist/services/school.js.map +1 -1
- package/dist/services/studentImport.d.ts +34 -0
- package/dist/services/studentImport.d.ts.map +1 -0
- package/dist/services/studentImport.js +236 -0
- package/dist/services/studentImport.js.map +1 -0
- package/dist/services/transcript.d.ts +61 -0
- package/dist/services/transcript.d.ts.map +1 -0
- package/dist/services/transcript.js +243 -0
- package/dist/services/transcript.js.map +1 -0
- package/dist/services/worksheet.d.ts +22 -0
- package/dist/services/worksheet.d.ts.map +1 -1
- package/dist/services/worksheet.js +94 -2
- package/dist/services/worksheet.js.map +1 -1
- package/dist/trpc.d.ts +1 -1
- package/dist/trpc.d.ts.map +1 -1
- package/dist/trpc.js +5 -5
- package/dist/trpc.js.map +1 -1
- package/dist/utils/adminPanelAccess.d.ts +4 -5
- package/dist/utils/adminPanelAccess.d.ts.map +1 -1
- package/dist/utils/adminPanelAccess.js +12 -34
- package/dist/utils/adminPanelAccess.js.map +1 -1
- package/package.json +2 -1
- package/prisma/migrations/20260804120000_add_attendance_records_and_excusals/migration.sql +114 -0
- package/prisma/migrations/20260804120100_add_fee_plans_invoices_payments/migration.sql +101 -0
- package/prisma/migrations/20260804120200_add_marketplace_listings/migration.sql +54 -0
- package/prisma/migrations/20260804120300_content_platform_b3_b9/migration.sql +63 -0
- package/prisma/migrations/20260804120400_c_academic_ops/migration.sql +77 -0
- package/prisma/schema.prisma +371 -11
- package/scripts/import-nepal-cdc-curriculum.ts +379 -0
- package/src/lib/assignmentTypeDefaults.ts +222 -0
- package/src/lib/config/env.ts +0 -3
- package/src/lib/feePdf.ts +199 -0
- package/src/lib/feePdfFormat.ts +14 -0
- package/src/lib/idCardPdf.ts +336 -0
- package/src/lib/pdfLetterhead.ts +124 -0
- package/src/lib/transcriptPdf.ts +502 -0
- package/src/models/academicCalendar.ts +139 -0
- package/src/models/attendance.ts +704 -73
- package/src/models/class.ts +1 -0
- package/src/models/fees.ts +349 -0
- package/src/models/marketplace.ts +158 -0
- package/src/models/moduleItem.ts +107 -0
- package/src/models/school.ts +95 -0
- package/src/models/worksheet.ts +2 -0
- package/src/routers/_app.ts +10 -0
- package/src/routers/academicCalendar.ts +103 -0
- package/src/routers/assignment.ts +8 -0
- package/src/routers/attendance.ts +7 -3
- package/src/routers/class.ts +4 -0
- package/src/routers/fees.ts +126 -0
- package/src/routers/marketplace.ts +134 -0
- package/src/routers/moduleItem.ts +89 -0
- package/src/routers/parent.ts +68 -0
- package/src/routers/school.ts +184 -1
- package/src/routers/worksheet.ts +11 -0
- package/src/services/academicCalendar.ts +299 -0
- package/src/services/assignment.ts +122 -12
- package/src/services/attendance.ts +548 -26
- package/src/services/attendanceMerge.ts +139 -0
- package/src/services/attendanceRate.ts +41 -0
- package/src/services/attendanceStats.ts +63 -0
- package/src/services/class.ts +25 -1
- package/src/services/fees.ts +424 -0
- package/src/services/feesAuthz.ts +77 -0
- package/src/services/gradebook.ts +70 -0
- package/src/services/idCard.ts +147 -0
- package/src/services/marketplace.ts +785 -0
- package/src/services/moduleItem.ts +274 -0
- package/src/services/notification.ts +43 -0
- package/src/services/parentPortal.ts +380 -0
- package/src/services/school.ts +243 -45
- package/src/services/studentImport.ts +273 -0
- package/src/services/transcript.ts +313 -0
- package/src/services/worksheet.ts +111 -0
- package/src/trpc.ts +3 -3
- package/src/utils/adminPanelAccess.ts +12 -35
- package/tests/unit/assignmentTypeDefaults.test.ts +52 -0
- package/tests/unit/lib/feePdf.test.ts +15 -0
- package/tests/unit/lib/idCardPdf.test.ts +40 -0
- package/tests/unit/lib/transcriptPdf.test.ts +111 -0
- package/tests/unit/services/attendanceMerge.test.ts +90 -0
- package/tests/unit/services/attendanceRate.test.ts +38 -0
- 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
|
|
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;IACf,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC;;;;;;;;;;;;;GA+BF;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;IAC7B,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC;;;;;;;;;;;;;GAkCF;AAED,sFAAsF;AACtF,wBAAsB,yBAAyB,CAC7C,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,MAAM;;;;;;;;;;;;;KA8Df;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"}
|
|
@@ -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]="
|
|
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=
|
|
599
|
+
//# debugId=8196c4a5-acef-500c-8732-025782486e87
|