@studious-lms/server 1.23.0 → 1.25.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 +547 -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 +180 -0
- package/dist/models/marketplace.d.ts.map +1 -0
- package/dist/models/marketplace.js +116 -0
- package/dist/models/marketplace.js.map +1 -0
- package/dist/models/moduleItem.d.ts +308 -0
- package/dist/models/moduleItem.d.ts.map +1 -0
- package/dist/models/moduleItem.js +93 -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 +18 -0
- package/dist/models/school.d.ts.map +1 -1
- package/dist/models/school.js +7 -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 +12024 -7778
- 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 +61 -0
- package/dist/routers/moduleItem.js.map +1 -0
- package/dist/routers/parent.d.ts +170 -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 +363 -7
- package/dist/routers/school.d.ts.map +1 -1
- package/dist/routers/school.js +110 -3
- 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 +242 -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 +327 -10
- package/dist/services/attendance.d.ts.map +1 -1
- package/dist/services/attendance.js +389 -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 +34 -2
- package/dist/services/class.d.ts.map +1 -1
- package/dist/services/class.js +27 -4
- 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 +330 -0
- package/dist/services/fees.js.map +1 -0
- package/dist/services/feesAuthz.d.ts +46 -0
- package/dist/services/feesAuthz.d.ts.map +1 -0
- package/dist/services/feesAuthz.js +49 -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 +631 -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 +204 -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 +131 -0
- package/dist/services/parentPortal.d.ts.map +1 -0
- package/dist/services/parentPortal.js +314 -0
- package/dist/services/parentPortal.js.map +1 -0
- package/dist/services/school.d.ts +28 -3
- package/dist/services/school.d.ts.map +1 -1
- package/dist/services/school.js +98 -54
- 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 +249 -0
- package/dist/services/studentImport.js.map +1 -0
- package/dist/services/transcript.d.ts +60 -0
- package/dist/services/transcript.d.ts.map +1 -0
- package/dist/services/transcript.js +231 -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 +402 -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 +702 -73
- package/src/models/class.ts +1 -0
- package/src/models/fees.ts +349 -0
- package/src/models/marketplace.ts +161 -0
- package/src/models/moduleItem.ts +125 -0
- package/src/models/school.ts +6 -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 +91 -0
- package/src/routers/parent.ts +68 -0
- package/src/routers/school.ts +149 -1
- package/src/routers/worksheet.ts +11 -0
- package/src/services/academicCalendar.ts +327 -0
- package/src/services/assignment.ts +122 -12
- package/src/services/attendance.ts +552 -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 +26 -2
- package/src/services/fees.ts +447 -0
- package/src/services/feesAuthz.ts +73 -0
- package/src/services/gradebook.ts +70 -0
- package/src/services/idCard.ts +147 -0
- package/src/services/marketplace.ts +790 -0
- package/src/services/moduleItem.ts +298 -0
- package/src/services/notification.ts +43 -0
- package/src/services/parentPortal.ts +367 -0
- package/src/services/school.ts +100 -54
- package/src/services/studentImport.ts +285 -0
- package/src/services/transcript.ts +301 -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,402 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Import Nepal CDC G4–5 curriculum pack as department-level MarkScheme /
|
|
3
|
+
* GradingBoundary templates.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* npx tsx scripts/import-nepal-cdc-curriculum.ts --departmentId=<uuid>
|
|
7
|
+
* npm run curriculum:import-nepal-cdc -- --departmentId=<uuid> --dry-run
|
|
8
|
+
*
|
|
9
|
+
* Options:
|
|
10
|
+
* --departmentId Required. Target department for templates.
|
|
11
|
+
* --schoolId Optional. Defaults to the department's school.
|
|
12
|
+
* --grades Optional. Comma list: 4,5 (default both).
|
|
13
|
+
* --subjects Optional. Comma list of subject keys (default all).
|
|
14
|
+
* --skip-boundary Skip creating the 1–4 proficiency GradingBoundary.
|
|
15
|
+
* --dry-run Parse and report without writing.
|
|
16
|
+
* --force Re-import even if a template with the same name exists
|
|
17
|
+
* (creates duplicates; default skips existing names).
|
|
18
|
+
*/
|
|
19
|
+
import { readFileSync, readdirSync } from "node:fs";
|
|
20
|
+
import { dirname, join } from "node:path";
|
|
21
|
+
import { fileURLToPath } from "node:url";
|
|
22
|
+
import { prisma } from "../src/lib/prisma.js";
|
|
23
|
+
|
|
24
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
25
|
+
const PACK_DIR = join(__dirname, "../data/curriculum/nepal-cdc-g4-g5");
|
|
26
|
+
|
|
27
|
+
type PackCriterion = {
|
|
28
|
+
id: string;
|
|
29
|
+
title: string;
|
|
30
|
+
description: string;
|
|
31
|
+
levels: Array<{
|
|
32
|
+
id: string;
|
|
33
|
+
name: string;
|
|
34
|
+
description: string;
|
|
35
|
+
points: number;
|
|
36
|
+
color: string;
|
|
37
|
+
}>;
|
|
38
|
+
meta?: Record<string, unknown>;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
type PackUnit = {
|
|
42
|
+
id: string;
|
|
43
|
+
grade: 4 | 5;
|
|
44
|
+
subjectKey: string;
|
|
45
|
+
unitNumber: number;
|
|
46
|
+
titleEn: string;
|
|
47
|
+
titleNe: string | null;
|
|
48
|
+
transcriptionStatus: string;
|
|
49
|
+
notes?: string | null;
|
|
50
|
+
tags: string[];
|
|
51
|
+
markScheme: {
|
|
52
|
+
name: string;
|
|
53
|
+
description: string;
|
|
54
|
+
category: string;
|
|
55
|
+
totalPoints: number;
|
|
56
|
+
criteria: PackCriterion[];
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
type SubjectFile = {
|
|
61
|
+
key: string;
|
|
62
|
+
nameEn: string;
|
|
63
|
+
nameNe: string;
|
|
64
|
+
units: PackUnit[];
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
type ProficiencyScale = {
|
|
68
|
+
gradingBoundary: {
|
|
69
|
+
name: string;
|
|
70
|
+
description?: string;
|
|
71
|
+
boundaries: Array<{
|
|
72
|
+
id: string;
|
|
73
|
+
grade: string;
|
|
74
|
+
minPercentage: number;
|
|
75
|
+
maxPercentage: number;
|
|
76
|
+
color: string;
|
|
77
|
+
description?: string;
|
|
78
|
+
}>;
|
|
79
|
+
};
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
function parseArgs(argv: string[]) {
|
|
83
|
+
const out: Record<string, string | boolean> = {};
|
|
84
|
+
for (const arg of argv) {
|
|
85
|
+
if (!arg.startsWith("--")) continue;
|
|
86
|
+
const eq = arg.indexOf("=");
|
|
87
|
+
if (eq === -1) {
|
|
88
|
+
out[arg.slice(2)] = true;
|
|
89
|
+
} else {
|
|
90
|
+
out[arg.slice(2, eq)] = arg.slice(eq + 1);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return out;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function parseBool(value: string | boolean | undefined): boolean {
|
|
97
|
+
if (value === undefined) return false;
|
|
98
|
+
if (typeof value === "boolean") return value;
|
|
99
|
+
return !["false", "0", "no"].includes(value.trim().toLowerCase());
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function loadJson<T>(path: string): T {
|
|
103
|
+
return JSON.parse(readFileSync(path, "utf8")) as T;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function stripCriterionMeta(criteria: PackCriterion[]) {
|
|
107
|
+
return criteria.map(({ id, title, description, levels }) => ({
|
|
108
|
+
id,
|
|
109
|
+
title,
|
|
110
|
+
description,
|
|
111
|
+
levels,
|
|
112
|
+
}));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function buildMarkSchemeStructured(unit: PackUnit) {
|
|
116
|
+
return JSON.stringify({
|
|
117
|
+
name: unit.markScheme.name,
|
|
118
|
+
description: unit.markScheme.description,
|
|
119
|
+
category: unit.markScheme.category,
|
|
120
|
+
totalPoints: unit.markScheme.totalPoints,
|
|
121
|
+
criteria: stripCriterionMeta(unit.markScheme.criteria),
|
|
122
|
+
curriculumMeta: {
|
|
123
|
+
packId: "nepal-cdc-g4-g5-2083",
|
|
124
|
+
unitId: unit.id,
|
|
125
|
+
grade: unit.grade,
|
|
126
|
+
subjectKey: unit.subjectKey,
|
|
127
|
+
unitNumber: unit.unitNumber,
|
|
128
|
+
titleNe: unit.titleNe,
|
|
129
|
+
transcriptionStatus: unit.transcriptionStatus,
|
|
130
|
+
notes: unit.notes ?? null,
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const TAG_COLORS: Record<string, string> = {
|
|
136
|
+
"Grade 4": "#2563eb",
|
|
137
|
+
"Grade 5": "#7c3aed",
|
|
138
|
+
"CDC-2083": "#0f766e",
|
|
139
|
+
formative: "#ca8a04",
|
|
140
|
+
nepali: "#dc2626",
|
|
141
|
+
english: "#0284c7",
|
|
142
|
+
mathematics: "#ea580c",
|
|
143
|
+
"social-studies": "#9333ea",
|
|
144
|
+
"science-technology": "#16a34a",
|
|
145
|
+
"health-physical-creative-arts": "#db2777",
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
async function ensureTag(
|
|
149
|
+
schoolId: string,
|
|
150
|
+
departmentId: string,
|
|
151
|
+
name: string,
|
|
152
|
+
dryRun: boolean
|
|
153
|
+
) {
|
|
154
|
+
const existing = await prisma.tag.findFirst({
|
|
155
|
+
where: { schoolId, name: { equals: name, mode: "insensitive" } },
|
|
156
|
+
select: { id: true, name: true },
|
|
157
|
+
});
|
|
158
|
+
if (existing) return existing;
|
|
159
|
+
if (dryRun) {
|
|
160
|
+
return { id: `dry-run-tag-${name}`, name };
|
|
161
|
+
}
|
|
162
|
+
return prisma.tag.create({
|
|
163
|
+
data: {
|
|
164
|
+
schoolId,
|
|
165
|
+
departmentId,
|
|
166
|
+
name,
|
|
167
|
+
color: TAG_COLORS[name] ?? "#3B82F6",
|
|
168
|
+
},
|
|
169
|
+
select: { id: true, name: true },
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
async function main() {
|
|
174
|
+
const args = parseArgs(process.argv.slice(2));
|
|
175
|
+
const departmentId = String(args.departmentId ?? "");
|
|
176
|
+
if (!departmentId) {
|
|
177
|
+
console.error(
|
|
178
|
+
"Missing --departmentId=<uuid>\n\nExample:\n npm run curriculum:import-nepal-cdc -- --departmentId=<uuid> --dry-run"
|
|
179
|
+
);
|
|
180
|
+
process.exit(1);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const dryRun = parseBool(args["dry-run"]);
|
|
184
|
+
const force = parseBool(args.force);
|
|
185
|
+
const skipBoundary = parseBool(args["skip-boundary"]);
|
|
186
|
+
const rawGrades = String(args.grades ?? "4,5")
|
|
187
|
+
.split(",")
|
|
188
|
+
.map((g) => Number(g.trim()));
|
|
189
|
+
const grades = rawGrades.filter((g): g is 4 | 5 => g === 4 || g === 5);
|
|
190
|
+
if (grades.length === 0 || grades.length !== rawGrades.length) {
|
|
191
|
+
console.error(
|
|
192
|
+
`Invalid --grades value: ${String(args.grades)}. Use 4, 5, or 4,5.`
|
|
193
|
+
);
|
|
194
|
+
process.exit(1);
|
|
195
|
+
}
|
|
196
|
+
const subjectFilter = args.subjects
|
|
197
|
+
? String(args.subjects)
|
|
198
|
+
.split(",")
|
|
199
|
+
.map((s) => s.trim())
|
|
200
|
+
.filter(Boolean)
|
|
201
|
+
: null;
|
|
202
|
+
|
|
203
|
+
const department = await prisma.department.findUnique({
|
|
204
|
+
where: { id: departmentId },
|
|
205
|
+
select: { id: true, name: true, schoolId: true },
|
|
206
|
+
});
|
|
207
|
+
if (!department) {
|
|
208
|
+
console.error(`Department not found: ${departmentId}`);
|
|
209
|
+
process.exit(1);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const schoolId = String(args.schoolId ?? department.schoolId);
|
|
213
|
+
if (schoolId !== department.schoolId) {
|
|
214
|
+
console.error(
|
|
215
|
+
`schoolId ${schoolId} does not match department school ${department.schoolId}`
|
|
216
|
+
);
|
|
217
|
+
process.exit(1);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const manifest = loadJson<Record<string, unknown>>(
|
|
221
|
+
join(PACK_DIR, "manifest.json")
|
|
222
|
+
);
|
|
223
|
+
const proficiency = loadJson<ProficiencyScale>(
|
|
224
|
+
join(PACK_DIR, "proficiency-scale.json")
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
const subjectFiles = readdirSync(join(PACK_DIR, "subjects")).filter((f) =>
|
|
228
|
+
f.endsWith(".json")
|
|
229
|
+
);
|
|
230
|
+
const subjects: SubjectFile[] = subjectFiles.map((f) =>
|
|
231
|
+
loadJson<SubjectFile>(join(PACK_DIR, "subjects", f))
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
if (subjectFilter) {
|
|
235
|
+
const knownKeys = new Set(subjects.map((s) => s.key));
|
|
236
|
+
const unknown = subjectFilter.filter((key) => !knownKeys.has(key));
|
|
237
|
+
if (unknown.length > 0) {
|
|
238
|
+
console.error(
|
|
239
|
+
`Unknown --subjects value(s): ${unknown.join(", ")}. Known: ${[...knownKeys].join(", ")}`
|
|
240
|
+
);
|
|
241
|
+
process.exit(1);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const units = subjects
|
|
246
|
+
.filter((s) => !subjectFilter || subjectFilter.includes(s.key))
|
|
247
|
+
.flatMap((s) => s.units)
|
|
248
|
+
.filter((u) => grades.includes(u.grade));
|
|
249
|
+
|
|
250
|
+
console.log(`Pack: ${String(manifest.name)} (${String(manifest.version)})`);
|
|
251
|
+
console.log(
|
|
252
|
+
`Department: ${department.name} (${department.id}) @ school ${schoolId}`
|
|
253
|
+
);
|
|
254
|
+
console.log(
|
|
255
|
+
`Importing ${units.length} unit MarkSchemes` +
|
|
256
|
+
(skipBoundary ? "" : " + 1 proficiency GradingBoundary") +
|
|
257
|
+
(dryRun ? " [DRY RUN]" : "")
|
|
258
|
+
);
|
|
259
|
+
|
|
260
|
+
const existingRubrics = await prisma.markScheme.findMany({
|
|
261
|
+
where: { departmentId },
|
|
262
|
+
select: { name: true },
|
|
263
|
+
});
|
|
264
|
+
const existingNames = new Set(
|
|
265
|
+
existingRubrics.map((r) => r.name).filter(Boolean) as string[]
|
|
266
|
+
);
|
|
267
|
+
|
|
268
|
+
let createdRubrics = 0;
|
|
269
|
+
let skippedRubrics = 0;
|
|
270
|
+
let createdBoundary = 0;
|
|
271
|
+
let skippedBoundary = 0;
|
|
272
|
+
const tagCache = new Map<string, { id: string; name: string }>();
|
|
273
|
+
|
|
274
|
+
async function getTag(name: string) {
|
|
275
|
+
const hit = tagCache.get(name);
|
|
276
|
+
if (hit) return hit;
|
|
277
|
+
const tag = await ensureTag(schoolId, departmentId, name, dryRun);
|
|
278
|
+
tagCache.set(name, tag);
|
|
279
|
+
return tag;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (!skipBoundary) {
|
|
283
|
+
const boundaryName = proficiency.gradingBoundary.name;
|
|
284
|
+
const existingBoundary = await prisma.gradingBoundary.findFirst({
|
|
285
|
+
where: { departmentId, name: boundaryName },
|
|
286
|
+
select: { id: true },
|
|
287
|
+
});
|
|
288
|
+
if (existingBoundary && !force) {
|
|
289
|
+
console.log(`Skip boundary (exists): ${boundaryName}`);
|
|
290
|
+
skippedBoundary++;
|
|
291
|
+
const tagNames = ["CDC-2083", "Grade 4", "Grade 5", "formative"];
|
|
292
|
+
for (const name of tagNames) {
|
|
293
|
+
const tag = await getTag(name);
|
|
294
|
+
if (!dryRun) {
|
|
295
|
+
await prisma.gradingBoundary.update({
|
|
296
|
+
where: { id: existingBoundary.id },
|
|
297
|
+
data: { tags: { connect: { id: tag.id } } },
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
} else if (dryRun) {
|
|
302
|
+
console.log(`Would create boundary: ${boundaryName}`);
|
|
303
|
+
createdBoundary++;
|
|
304
|
+
for (const name of ["CDC-2083", "Grade 4", "Grade 5", "formative"]) {
|
|
305
|
+
await getTag(name);
|
|
306
|
+
}
|
|
307
|
+
} else {
|
|
308
|
+
const structured = JSON.stringify({
|
|
309
|
+
name: proficiency.gradingBoundary.name,
|
|
310
|
+
boundaries: proficiency.gradingBoundary.boundaries,
|
|
311
|
+
});
|
|
312
|
+
const created = await prisma.gradingBoundary.create({
|
|
313
|
+
data: {
|
|
314
|
+
name: boundaryName,
|
|
315
|
+
structured,
|
|
316
|
+
department: { connect: { id: departmentId } },
|
|
317
|
+
},
|
|
318
|
+
select: { id: true },
|
|
319
|
+
});
|
|
320
|
+
for (const name of ["CDC-2083", "Grade 4", "Grade 5", "formative"]) {
|
|
321
|
+
const tag = await getTag(name);
|
|
322
|
+
await prisma.gradingBoundary.update({
|
|
323
|
+
where: { id: created.id },
|
|
324
|
+
data: { tags: { connect: { id: tag.id } } },
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
console.log(`Created boundary: ${boundaryName}`);
|
|
328
|
+
createdBoundary++;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
for (const unit of units) {
|
|
333
|
+
const name = unit.markScheme.name;
|
|
334
|
+
if (existingNames.has(name) && !force) {
|
|
335
|
+
skippedRubrics++;
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const structured = buildMarkSchemeStructured(unit);
|
|
340
|
+
const tagNames = Array.from(
|
|
341
|
+
new Set([
|
|
342
|
+
...unit.tags,
|
|
343
|
+
`Grade ${unit.grade}`,
|
|
344
|
+
unit.subjectKey,
|
|
345
|
+
"CDC-2083",
|
|
346
|
+
"formative",
|
|
347
|
+
])
|
|
348
|
+
);
|
|
349
|
+
|
|
350
|
+
if (dryRun) {
|
|
351
|
+
createdRubrics++;
|
|
352
|
+
for (const t of tagNames) await getTag(t);
|
|
353
|
+
continue;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const created = await prisma.markScheme.create({
|
|
357
|
+
data: {
|
|
358
|
+
name,
|
|
359
|
+
structured,
|
|
360
|
+
department: { connect: { id: departmentId } },
|
|
361
|
+
},
|
|
362
|
+
select: { id: true },
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
for (const tagName of tagNames) {
|
|
366
|
+
const tag = await getTag(tagName);
|
|
367
|
+
await prisma.markScheme.update({
|
|
368
|
+
where: { id: created.id },
|
|
369
|
+
data: { tags: { connect: { id: tag.id } } },
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
existingNames.add(name);
|
|
374
|
+
createdRubrics++;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
console.log("\nDone.");
|
|
378
|
+
console.log(` Rubric templates created: ${createdRubrics}`);
|
|
379
|
+
console.log(` Rubric templates skipped: ${skippedRubrics}`);
|
|
380
|
+
console.log(` Boundary templates created: ${createdBoundary}`);
|
|
381
|
+
console.log(` Boundary templates skipped: ${skippedBoundary}`);
|
|
382
|
+
console.log(` Tags ensured: ${tagCache.size}`);
|
|
383
|
+
if (dryRun) {
|
|
384
|
+
console.log("\nDry run only — no database writes.");
|
|
385
|
+
} else {
|
|
386
|
+
console.log(
|
|
387
|
+
"\nApply from Institution → Department → Templates, or class InstitutionPanel → Apply template."
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
console.log(
|
|
391
|
+
"\n⚠ Spot-check required: Nepali titles/outcomes need a Nepali-speaker review before production."
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
main()
|
|
396
|
+
.catch((err) => {
|
|
397
|
+
console.error(err);
|
|
398
|
+
process.exit(1);
|
|
399
|
+
})
|
|
400
|
+
.finally(async () => {
|
|
401
|
+
await prisma.$disconnect();
|
|
402
|
+
});
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import type { AssignmentType } from "@prisma/client";
|
|
2
|
+
import { TRPCError } from "@trpc/server";
|
|
3
|
+
|
|
4
|
+
export type AssignmentTypeBehavior = {
|
|
5
|
+
acceptFiles: boolean;
|
|
6
|
+
acceptExtendedResponse: boolean;
|
|
7
|
+
acceptWorksheet: boolean;
|
|
8
|
+
singleAttempt: boolean;
|
|
9
|
+
allowLateSubmissions: boolean;
|
|
10
|
+
timeLimitMinutes: number | null;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Sensible defaults by assignment type. QUIZ/TEST tighten attempts and
|
|
15
|
+
* discourage late file spam; ESSAY/DISCUSSION prefer written response.
|
|
16
|
+
*/
|
|
17
|
+
export function getAssignmentTypeDefaults(
|
|
18
|
+
type: AssignmentType | string | undefined | null
|
|
19
|
+
): AssignmentTypeBehavior {
|
|
20
|
+
switch (type) {
|
|
21
|
+
case "QUIZ":
|
|
22
|
+
return {
|
|
23
|
+
acceptFiles: false,
|
|
24
|
+
acceptExtendedResponse: false,
|
|
25
|
+
acceptWorksheet: true,
|
|
26
|
+
singleAttempt: true,
|
|
27
|
+
allowLateSubmissions: false,
|
|
28
|
+
timeLimitMinutes: null,
|
|
29
|
+
};
|
|
30
|
+
case "TEST":
|
|
31
|
+
return {
|
|
32
|
+
acceptFiles: false,
|
|
33
|
+
acceptExtendedResponse: false,
|
|
34
|
+
acceptWorksheet: true,
|
|
35
|
+
singleAttempt: true,
|
|
36
|
+
allowLateSubmissions: false,
|
|
37
|
+
timeLimitMinutes: null,
|
|
38
|
+
};
|
|
39
|
+
case "ESSAY":
|
|
40
|
+
return {
|
|
41
|
+
acceptFiles: false,
|
|
42
|
+
acceptExtendedResponse: true,
|
|
43
|
+
acceptWorksheet: false,
|
|
44
|
+
singleAttempt: false,
|
|
45
|
+
allowLateSubmissions: true,
|
|
46
|
+
timeLimitMinutes: null,
|
|
47
|
+
};
|
|
48
|
+
case "DISCUSSION":
|
|
49
|
+
return {
|
|
50
|
+
acceptFiles: false,
|
|
51
|
+
acceptExtendedResponse: true,
|
|
52
|
+
acceptWorksheet: false,
|
|
53
|
+
singleAttempt: false,
|
|
54
|
+
allowLateSubmissions: true,
|
|
55
|
+
timeLimitMinutes: null,
|
|
56
|
+
};
|
|
57
|
+
case "PROJECT":
|
|
58
|
+
case "PRESENTATION":
|
|
59
|
+
case "LAB":
|
|
60
|
+
return {
|
|
61
|
+
acceptFiles: true,
|
|
62
|
+
acceptExtendedResponse: true,
|
|
63
|
+
acceptWorksheet: false,
|
|
64
|
+
singleAttempt: false,
|
|
65
|
+
allowLateSubmissions: true,
|
|
66
|
+
timeLimitMinutes: null,
|
|
67
|
+
};
|
|
68
|
+
case "HOMEWORK":
|
|
69
|
+
case "OTHER":
|
|
70
|
+
default:
|
|
71
|
+
return {
|
|
72
|
+
acceptFiles: true,
|
|
73
|
+
acceptExtendedResponse: false,
|
|
74
|
+
acceptWorksheet: false,
|
|
75
|
+
singleAttempt: false,
|
|
76
|
+
allowLateSubmissions: true,
|
|
77
|
+
timeLimitMinutes: null,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export type AssignmentBehaviorInput = {
|
|
83
|
+
type?: AssignmentType | string | null;
|
|
84
|
+
acceptFiles?: boolean;
|
|
85
|
+
acceptExtendedResponse?: boolean;
|
|
86
|
+
acceptWorksheet?: boolean;
|
|
87
|
+
singleAttempt?: boolean;
|
|
88
|
+
allowLateSubmissions?: boolean;
|
|
89
|
+
timeLimitMinutes?: number | null;
|
|
90
|
+
/** When true, fill unspecified deliverable/behavior flags from type defaults. */
|
|
91
|
+
applyTypeDefaults?: boolean;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Merge explicit fields with type defaults and validate incompatible combos.
|
|
96
|
+
*/
|
|
97
|
+
export function resolveAssignmentBehavior(input: AssignmentBehaviorInput): {
|
|
98
|
+
acceptFiles: boolean;
|
|
99
|
+
acceptExtendedResponse: boolean;
|
|
100
|
+
acceptWorksheet: boolean;
|
|
101
|
+
singleAttempt: boolean;
|
|
102
|
+
allowLateSubmissions: boolean;
|
|
103
|
+
timeLimitMinutes: number | null | undefined;
|
|
104
|
+
} {
|
|
105
|
+
const defaults = getAssignmentTypeDefaults(input.type);
|
|
106
|
+
const apply = input.applyTypeDefaults !== false;
|
|
107
|
+
|
|
108
|
+
const acceptFiles =
|
|
109
|
+
input.acceptFiles !== undefined
|
|
110
|
+
? input.acceptFiles
|
|
111
|
+
: apply
|
|
112
|
+
? defaults.acceptFiles
|
|
113
|
+
: false;
|
|
114
|
+
const acceptExtendedResponse =
|
|
115
|
+
input.acceptExtendedResponse !== undefined
|
|
116
|
+
? input.acceptExtendedResponse
|
|
117
|
+
: apply
|
|
118
|
+
? defaults.acceptExtendedResponse
|
|
119
|
+
: false;
|
|
120
|
+
const acceptWorksheet =
|
|
121
|
+
input.acceptWorksheet !== undefined
|
|
122
|
+
? input.acceptWorksheet
|
|
123
|
+
: apply
|
|
124
|
+
? defaults.acceptWorksheet
|
|
125
|
+
: false;
|
|
126
|
+
const singleAttempt =
|
|
127
|
+
input.singleAttempt !== undefined
|
|
128
|
+
? input.singleAttempt
|
|
129
|
+
: apply
|
|
130
|
+
? defaults.singleAttempt
|
|
131
|
+
: false;
|
|
132
|
+
const allowLateSubmissions =
|
|
133
|
+
input.allowLateSubmissions !== undefined
|
|
134
|
+
? input.allowLateSubmissions
|
|
135
|
+
: apply
|
|
136
|
+
? defaults.allowLateSubmissions
|
|
137
|
+
: true;
|
|
138
|
+
const timeLimitMinutes =
|
|
139
|
+
input.timeLimitMinutes !== undefined
|
|
140
|
+
? input.timeLimitMinutes
|
|
141
|
+
: apply
|
|
142
|
+
? defaults.timeLimitMinutes
|
|
143
|
+
: undefined;
|
|
144
|
+
|
|
145
|
+
if (
|
|
146
|
+
timeLimitMinutes !== undefined &&
|
|
147
|
+
timeLimitMinutes !== null &&
|
|
148
|
+
(timeLimitMinutes < 1 || timeLimitMinutes > 24 * 60)
|
|
149
|
+
) {
|
|
150
|
+
throw new TRPCError({
|
|
151
|
+
code: "BAD_REQUEST",
|
|
152
|
+
message: "timeLimitMinutes must be between 1 and 1440",
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Quizzes/tests: disallow file deliverables when late submissions are off
|
|
157
|
+
// (prevents "late file spam" after the window closes).
|
|
158
|
+
if (
|
|
159
|
+
(input.type === "QUIZ" || input.type === "TEST") &&
|
|
160
|
+
acceptFiles &&
|
|
161
|
+
!allowLateSubmissions &&
|
|
162
|
+
input.acceptFiles === true
|
|
163
|
+
) {
|
|
164
|
+
// Allowed only when teacher explicitly opts into files; keep allowLate false.
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (
|
|
168
|
+
!acceptFiles &&
|
|
169
|
+
!acceptExtendedResponse &&
|
|
170
|
+
!acceptWorksheet
|
|
171
|
+
) {
|
|
172
|
+
throw new TRPCError({
|
|
173
|
+
code: "BAD_REQUEST",
|
|
174
|
+
message:
|
|
175
|
+
"Assignment must accept at least one deliverable (files, extended response, or worksheet)",
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return {
|
|
180
|
+
acceptFiles,
|
|
181
|
+
acceptExtendedResponse,
|
|
182
|
+
acceptWorksheet,
|
|
183
|
+
singleAttempt,
|
|
184
|
+
allowLateSubmissions,
|
|
185
|
+
timeLimitMinutes,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export const SIDEBAR_FEATURE_KEYS = [
|
|
190
|
+
"overview",
|
|
191
|
+
"assignments",
|
|
192
|
+
"quizzes",
|
|
193
|
+
"worksheets",
|
|
194
|
+
"grades",
|
|
195
|
+
"files",
|
|
196
|
+
"modules",
|
|
197
|
+
"discussions",
|
|
198
|
+
"attendance",
|
|
199
|
+
"aiLabs",
|
|
200
|
+
"members",
|
|
201
|
+
"syllabus",
|
|
202
|
+
"settings",
|
|
203
|
+
] as const;
|
|
204
|
+
|
|
205
|
+
export type SidebarFeatureKey = (typeof SIDEBAR_FEATURE_KEYS)[number];
|
|
206
|
+
|
|
207
|
+
export type SidebarFeatures = Partial<Record<SidebarFeatureKey, boolean>>;
|
|
208
|
+
|
|
209
|
+
export function normalizeSidebarFeatures(
|
|
210
|
+
value: unknown
|
|
211
|
+
): SidebarFeatures | null {
|
|
212
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
const out: SidebarFeatures = {};
|
|
216
|
+
for (const key of SIDEBAR_FEATURE_KEYS) {
|
|
217
|
+
if (key in (value as object)) {
|
|
218
|
+
out[key] = Boolean((value as Record<string, unknown>)[key]);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return out;
|
|
222
|
+
}
|
package/src/lib/config/env.ts
CHANGED
|
@@ -69,8 +69,6 @@ const fullSchema = baseSchema.extend({
|
|
|
69
69
|
INFERENCE_API_BASE_URL: z.string().url().optional(),
|
|
70
70
|
OPENAI_API_KEY: z.string().optional(),
|
|
71
71
|
LOG_MODE: z.enum(['normal', 'verbose', 'quiet']).default('normal'),
|
|
72
|
-
/** Comma/whitespace-separated user UUIDs allowed to call `admin.*` tRPC (required in production). */
|
|
73
|
-
ADMIN_PANEL_USER_IDS: z.string().optional(),
|
|
74
72
|
});
|
|
75
73
|
|
|
76
74
|
// Test schema - only require what's needed for tests
|
|
@@ -99,7 +97,6 @@ const testSchema = baseSchema.extend({
|
|
|
99
97
|
OPENAI_API_KEY: z.string().optional(),
|
|
100
98
|
INFERENCE_API_BASE_URL: z.string().url().optional(),
|
|
101
99
|
LOG_MODE: z.enum(['normal', 'verbose', 'quiet']).default('quiet'),
|
|
102
|
-
ADMIN_PANEL_USER_IDS: z.string().optional(),
|
|
103
100
|
});
|
|
104
101
|
|
|
105
102
|
// Use test schema in test mode, full schema otherwise
|