@studious-lms/server 1.21.0 → 1.23.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/dist/models/agenda.d.ts +6 -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 +27 -0
- package/dist/models/assignment.d.ts.map +1 -1
- package/dist/models/auth.d.ts +4 -0
- package/dist/models/auth.d.ts.map +1 -1
- package/dist/models/class.d.ts +14 -0
- package/dist/models/class.d.ts.map +1 -1
- package/dist/models/event.d.ts +11 -0
- package/dist/models/event.d.ts.map +1 -1
- package/dist/models/file.d.ts +19 -0
- package/dist/models/file.d.ts.map +1 -1
- package/dist/models/folder.d.ts +4 -0
- package/dist/models/folder.d.ts.map +1 -1
- package/dist/models/gradebook.d.ts +180 -0
- package/dist/models/gradebook.d.ts.map +1 -0
- package/dist/models/gradebook.js +88 -0
- package/dist/models/gradebook.js.map +1 -0
- package/dist/models/labChat.d.ts +4 -0
- package/dist/models/labChat.d.ts.map +1 -1
- package/dist/models/newtonChat.d.ts +2 -0
- package/dist/models/newtonChat.d.ts.map +1 -1
- package/dist/models/school.d.ts +177 -0
- package/dist/models/school.d.ts.map +1 -1
- package/dist/models/school.js +75 -2
- package/dist/models/school.js.map +1 -1
- package/dist/models/section.d.ts +3 -0
- package/dist/models/section.d.ts.map +1 -1
- package/dist/models/worksheet.d.ts +6 -0
- package/dist/models/worksheet.d.ts.map +1 -1
- package/dist/routers/_app.d.ts +1475 -73
- package/dist/routers/_app.d.ts.map +1 -1
- package/dist/routers/_app.js +4 -2
- package/dist/routers/_app.js.map +1 -1
- package/dist/routers/admin.d.ts +164 -0
- package/dist/routers/admin.d.ts.map +1 -1
- package/dist/routers/admin.js +23 -4
- package/dist/routers/admin.js.map +1 -1
- package/dist/routers/agenda.d.ts +6 -0
- package/dist/routers/agenda.d.ts.map +1 -1
- package/dist/routers/assignment.d.ts +30 -3
- package/dist/routers/assignment.d.ts.map +1 -1
- package/dist/routers/class.d.ts +17 -0
- package/dist/routers/class.d.ts.map +1 -1
- package/dist/routers/event.d.ts +4 -0
- package/dist/routers/event.d.ts.map +1 -1
- package/dist/routers/file.d.ts +66 -0
- package/dist/routers/file.d.ts.map +1 -1
- package/dist/routers/file.js +111 -2
- package/dist/routers/file.js.map +1 -1
- package/dist/routers/gradebook.d.ts +299 -0
- package/dist/routers/gradebook.d.ts.map +1 -0
- package/dist/routers/gradebook.js +107 -0
- package/dist/routers/gradebook.js.map +1 -0
- package/dist/routers/school.d.ts +118 -0
- package/dist/routers/school.d.ts.map +1 -1
- package/dist/routers/school.js +32 -3
- package/dist/routers/school.js.map +1 -1
- package/dist/routers/section.d.ts +1 -1
- package/dist/routers/worksheet.d.ts +3 -1
- package/dist/routers/worksheet.d.ts.map +1 -1
- package/dist/services/adminInstitution.d.ts +51 -0
- package/dist/services/adminInstitution.d.ts.map +1 -1
- package/dist/services/adminInstitution.js +52 -2
- package/dist/services/adminInstitution.js.map +1 -1
- package/dist/services/adminMetrics.d.ts +17 -0
- package/dist/services/adminMetrics.d.ts.map +1 -0
- package/dist/services/adminMetrics.js +37 -0
- package/dist/services/adminMetrics.js.map +1 -0
- package/dist/services/adminRunJob.d.ts +9 -0
- package/dist/services/adminRunJob.d.ts.map +1 -1
- package/dist/services/adminRunJob.js +18 -2
- package/dist/services/adminRunJob.js.map +1 -1
- package/dist/services/adminUser.d.ts +67 -0
- package/dist/services/adminUser.d.ts.map +1 -0
- package/dist/services/adminUser.js +87 -0
- package/dist/services/adminUser.js.map +1 -0
- package/dist/services/agenda.d.ts +6 -0
- package/dist/services/agenda.d.ts.map +1 -1
- package/dist/services/assignment.d.ts +27 -0
- package/dist/services/assignment.d.ts.map +1 -1
- package/dist/services/class.d.ts +17 -0
- package/dist/services/class.d.ts.map +1 -1
- package/dist/services/event.d.ts +4 -0
- package/dist/services/event.d.ts.map +1 -1
- package/dist/services/gradeCalculation.d.ts +70 -0
- package/dist/services/gradeCalculation.d.ts.map +1 -0
- package/dist/services/gradeCalculation.js +148 -0
- package/dist/services/gradeCalculation.js.map +1 -0
- package/dist/services/gradebook.d.ts +197 -0
- package/dist/services/gradebook.d.ts.map +1 -0
- package/dist/services/gradebook.js +263 -0
- package/dist/services/gradebook.js.map +1 -0
- package/dist/services/school.d.ts +98 -0
- package/dist/services/school.d.ts.map +1 -1
- package/dist/services/school.js +116 -4
- package/dist/services/school.js.map +1 -1
- package/dist/services/worksheet.d.ts +2 -0
- package/dist/services/worksheet.d.ts.map +1 -1
- package/package.json +1 -1
- package/prisma/migrations/20260708011629_add_gradebook_categories_columns_policies/migration.sql +78 -0
- package/prisma/migrations/20260723142314_make_file_annotation_submission_optional/migration.sql +2 -0
- package/prisma/schema.prisma +62 -2
- package/src/models/gradebook.ts +91 -0
- package/src/models/school.ts +89 -0
- package/src/routers/_app.ts +2 -0
- package/src/routers/admin.ts +28 -0
- package/src/routers/file.ts +128 -0
- package/src/routers/gradebook.ts +173 -0
- package/src/routers/school.ts +45 -0
- package/src/services/adminInstitution.ts +52 -0
- package/src/services/adminMetrics.ts +46 -0
- package/src/services/adminRunJob.ts +24 -0
- package/src/services/adminUser.ts +93 -0
- package/src/services/gradeCalculation.ts +253 -0
- package/src/services/gradebook.ts +383 -0
- package/src/services/school.ts +178 -1
- package/tests/unit/services/gradeCalculation.test.ts +206 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"class.d.ts","sourceRoot":"/","sources":["services/class.ts"],"names":[],"mappings":"AA4BA,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAgC/C,sFAAsF;AACtF,wBAAsB,aAAa,CAAC,MAAM,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDjD;AAED,0EAA0E;AAC1E,wBAAsB,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM
|
|
1
|
+
{"version":3,"file":"class.d.ts","sourceRoot":"/","sources":["services/class.ts"],"names":[],"mappings":"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"}
|
package/dist/services/event.d.ts
CHANGED
|
@@ -44,6 +44,8 @@ export declare function getEvent(userId: string, id: string): Promise<{
|
|
|
44
44
|
discussionsEnabled: boolean;
|
|
45
45
|
studentsCanCreateDiscussions: boolean;
|
|
46
46
|
studentsCanReply: boolean;
|
|
47
|
+
treatMissingAsZero: boolean;
|
|
48
|
+
gradeRoundingMode: import(".prisma/client").$Enums.GradeRoundingMode;
|
|
47
49
|
}) | null;
|
|
48
50
|
assignmentsAttached: {
|
|
49
51
|
type: import(".prisma/client").$Enums.AssignmentType;
|
|
@@ -170,6 +172,7 @@ export declare function attachAssignment(userId: string, eventId: string, assign
|
|
|
170
172
|
markSchemeId: string | null;
|
|
171
173
|
order: number | null;
|
|
172
174
|
gradingBoundaryId: string | null;
|
|
175
|
+
gradeCategoryId: string | null;
|
|
173
176
|
};
|
|
174
177
|
}>;
|
|
175
178
|
export declare function detachAssignment(userId: string, eventId: string, assignmentId: string): Promise<{
|
|
@@ -212,6 +215,7 @@ export declare function detachAssignment(userId: string, eventId: string, assign
|
|
|
212
215
|
markSchemeId: string | null;
|
|
213
216
|
order: number | null;
|
|
214
217
|
gradingBoundaryId: string | null;
|
|
218
|
+
gradeCategoryId: string | null;
|
|
215
219
|
};
|
|
216
220
|
}>;
|
|
217
221
|
export declare function getAvailableAssignments(userId: string, eventId: string): Promise<{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"event.d.ts","sourceRoot":"/","sources":["services/event.ts"],"names":[],"mappings":"AAmBA,6DAA6D;AAC7D,wBAAsB,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM
|
|
1
|
+
{"version":3,"file":"event.d.ts","sourceRoot":"/","sources":["services/event.ts"],"names":[],"mappings":"AAmBA,6DAA6D;AAC7D,wBAAsB,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAexD;AAED,wCAAwC;AACxC,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE;IACL,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;;;;;;;;;;;;;;;;GAwBF;AAED,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,MAAM,EACd,EAAE,EAAE,MAAM,EACV,IAAI,EAAE;IACJ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;;;;;;;;;;;;GA2BF;AAED,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM;;GAiBjE;AAuBD,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CrB;AAED,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkBrB;AAED,wBAAsB,uBAAuB,CAC3C,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;GAkBhB"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Grade calculation – pure functions for computing final class grades from
|
|
3
|
+
* categories, weights, and grade-calculation policies. No I/O; unit-testable.
|
|
4
|
+
*
|
|
5
|
+
* Future policies (standards-based grading, etc.) should extend
|
|
6
|
+
* `GradeCalculationPolicies` and be handled inside `computeFinalGrade` so
|
|
7
|
+
* callers never need to change.
|
|
8
|
+
*/
|
|
9
|
+
export type GradeRoundingMode = "NONE" | "HALF_UP" | "FLOOR" | "CEILING";
|
|
10
|
+
export interface GradeCalculationPolicies {
|
|
11
|
+
/** Count ungraded/missing scores as 0% instead of excluding them. */
|
|
12
|
+
treatMissingAsZero: boolean;
|
|
13
|
+
/** How the final percentage is rounded to a whole number. */
|
|
14
|
+
roundingMode: GradeRoundingMode;
|
|
15
|
+
}
|
|
16
|
+
export interface GradeCategoryInput {
|
|
17
|
+
id: string;
|
|
18
|
+
name: string;
|
|
19
|
+
/** Relative weight of this category toward the final grade. */
|
|
20
|
+
weight: number;
|
|
21
|
+
/** Number of lowest-percentage scores dropped within this category. */
|
|
22
|
+
dropLowest: number;
|
|
23
|
+
}
|
|
24
|
+
export interface ScoreInput {
|
|
25
|
+
/** Category the score belongs to; null falls into the default category. */
|
|
26
|
+
categoryId: string | null;
|
|
27
|
+
/** Points earned; null means missing/ungraded. */
|
|
28
|
+
pointsEarned: number | null;
|
|
29
|
+
/** Maximum points for the item; items with maxPoints <= 0 are ignored. */
|
|
30
|
+
maxPoints: number;
|
|
31
|
+
/** Relative weight of the item within its category (defaults to 1). */
|
|
32
|
+
weight?: number;
|
|
33
|
+
}
|
|
34
|
+
export interface CategoryAverage {
|
|
35
|
+
categoryId: string | null;
|
|
36
|
+
name: string;
|
|
37
|
+
weight: number;
|
|
38
|
+
/** Percentage 0–100, or null when no scores counted. */
|
|
39
|
+
average: number | null;
|
|
40
|
+
countedScores: number;
|
|
41
|
+
droppedScores: number;
|
|
42
|
+
}
|
|
43
|
+
export interface FinalGradeResult {
|
|
44
|
+
/** Final percentage 0–100 (rounded per policy), or null when no data. */
|
|
45
|
+
percentage: number | null;
|
|
46
|
+
categories: CategoryAverage[];
|
|
47
|
+
}
|
|
48
|
+
/** Synthetic category used for scores without a category. */
|
|
49
|
+
export declare const DEFAULT_CATEGORY: {
|
|
50
|
+
readonly id: null;
|
|
51
|
+
readonly weight: 1;
|
|
52
|
+
readonly dropLowest: 0;
|
|
53
|
+
};
|
|
54
|
+
export declare function roundPercentage(value: number, mode: GradeRoundingMode): number;
|
|
55
|
+
/**
|
|
56
|
+
* Computes the final class grade as the weighted average of category
|
|
57
|
+
* averages. Scores with no category fall into a default category with
|
|
58
|
+
* weight `DEFAULT_CATEGORY.weight`. Categories without counted scores are
|
|
59
|
+
* excluded from the weighted average.
|
|
60
|
+
*/
|
|
61
|
+
export declare function computeFinalGrade(policies: GradeCalculationPolicies, categories: GradeCategoryInput[], scores: ScoreInput[], options?: {
|
|
62
|
+
defaultCategoryName?: string;
|
|
63
|
+
}): FinalGradeResult;
|
|
64
|
+
/**
|
|
65
|
+
* Maps a percentage to a letter grade using a GradingBoundary `structured`
|
|
66
|
+
* JSON payload (`{ boundaries: [{ grade, minPercentage, maxPercentage }] }`).
|
|
67
|
+
* Returns null when the payload is unparseable or nothing matches.
|
|
68
|
+
*/
|
|
69
|
+
export declare function resolveLetterGrade(structured: string, percentage: number): string | null;
|
|
70
|
+
//# sourceMappingURL=gradeCalculation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gradeCalculation.d.ts","sourceRoot":"/","sources":["services/gradeCalculation.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,SAAS,GAAG,OAAO,GAAG,SAAS,CAAC;AAEzE,MAAM,WAAW,wBAAwB;IACvC,qEAAqE;IACrE,kBAAkB,EAAE,OAAO,CAAC;IAC5B,6DAA6D;IAC7D,YAAY,EAAE,iBAAiB,CAAC;CACjC;AAED,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,+DAA+D;IAC/D,MAAM,EAAE,MAAM,CAAC;IACf,uEAAuE;IACvE,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,UAAU;IACzB,2EAA2E;IAC3E,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,kDAAkD;IAClD,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,0EAA0E;IAC1E,SAAS,EAAE,MAAM,CAAC;IAClB,uEAAuE;IACvE,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,wDAAwD;IACxD,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,gBAAgB;IAC/B,yEAAyE;IACzE,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,EAAE,eAAe,EAAE,CAAC;CAC/B;AAED,6DAA6D;AAC7D,eAAO,MAAM,gBAAgB;;;;CAInB,CAAC;AAEX,wBAAgB,eAAe,CAC7B,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,iBAAiB,GACtB,MAAM,CAYR;AAsDD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,wBAAwB,EAClC,UAAU,EAAE,kBAAkB,EAAE,EAChC,MAAM,EAAE,UAAU,EAAE,EACpB,OAAO,CAAC,EAAE;IAAE,mBAAmB,CAAC,EAAE,MAAM,CAAA;CAAE,GACzC,gBAAgB,CAuElB;AAQD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,GACjB,MAAM,GAAG,IAAI,CAwBf"}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Grade calculation – pure functions for computing final class grades from
|
|
3
|
+
* categories, weights, and grade-calculation policies. No I/O; unit-testable.
|
|
4
|
+
*
|
|
5
|
+
* Future policies (standards-based grading, etc.) should extend
|
|
6
|
+
* `GradeCalculationPolicies` and be handled inside `computeFinalGrade` so
|
|
7
|
+
* callers never need to change.
|
|
8
|
+
*/
|
|
9
|
+
/** Synthetic category used for scores without a category. */
|
|
10
|
+
|
|
11
|
+
!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]="f05b8c2d-15ff-58b1-b01e-d40dba432326")}catch(e){}}();
|
|
12
|
+
export const DEFAULT_CATEGORY = {
|
|
13
|
+
id: null,
|
|
14
|
+
weight: 1,
|
|
15
|
+
dropLowest: 0,
|
|
16
|
+
};
|
|
17
|
+
export function roundPercentage(value, mode) {
|
|
18
|
+
switch (mode) {
|
|
19
|
+
case "HALF_UP":
|
|
20
|
+
return Math.round(value);
|
|
21
|
+
case "FLOOR":
|
|
22
|
+
return Math.floor(value);
|
|
23
|
+
case "CEILING":
|
|
24
|
+
return Math.ceil(value);
|
|
25
|
+
case "NONE":
|
|
26
|
+
default:
|
|
27
|
+
return value;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function collectCountableScores(scores, policies) {
|
|
31
|
+
const countable = [];
|
|
32
|
+
for (const score of scores) {
|
|
33
|
+
if (score.maxPoints <= 0)
|
|
34
|
+
continue;
|
|
35
|
+
if (score.pointsEarned === null) {
|
|
36
|
+
if (!policies.treatMissingAsZero)
|
|
37
|
+
continue;
|
|
38
|
+
countable.push({ percentage: 0, weight: score.weight ?? 1 });
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
countable.push({
|
|
42
|
+
percentage: (score.pointsEarned / score.maxPoints) * 100,
|
|
43
|
+
weight: score.weight ?? 1,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
return countable;
|
|
47
|
+
}
|
|
48
|
+
function averageCategory(countable, dropLowest) {
|
|
49
|
+
// Always keep at least one score so a category never collapses to null
|
|
50
|
+
// just because dropLowest >= score count.
|
|
51
|
+
const dropCount = Math.min(Math.max(dropLowest, 0), countable.length - 1);
|
|
52
|
+
const kept = dropCount > 0
|
|
53
|
+
? [...countable]
|
|
54
|
+
.sort((a, b) => a.percentage - b.percentage)
|
|
55
|
+
.slice(dropCount)
|
|
56
|
+
: countable;
|
|
57
|
+
const totalWeight = kept.reduce((sum, s) => sum + s.weight, 0);
|
|
58
|
+
if (kept.length === 0 || totalWeight <= 0) {
|
|
59
|
+
return { average: null, counted: 0, dropped: 0 };
|
|
60
|
+
}
|
|
61
|
+
const weighted = kept.reduce((sum, s) => sum + s.percentage * s.weight, 0);
|
|
62
|
+
return {
|
|
63
|
+
average: weighted / totalWeight,
|
|
64
|
+
counted: kept.length,
|
|
65
|
+
dropped: dropCount,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Computes the final class grade as the weighted average of category
|
|
70
|
+
* averages. Scores with no category fall into a default category with
|
|
71
|
+
* weight `DEFAULT_CATEGORY.weight`. Categories without counted scores are
|
|
72
|
+
* excluded from the weighted average.
|
|
73
|
+
*/
|
|
74
|
+
export function computeFinalGrade(policies, categories, scores, options) {
|
|
75
|
+
const knownIds = new Set(categories.map((c) => c.id));
|
|
76
|
+
const byCategory = new Map();
|
|
77
|
+
for (const score of scores) {
|
|
78
|
+
const key = score.categoryId !== null && knownIds.has(score.categoryId)
|
|
79
|
+
? score.categoryId
|
|
80
|
+
: null;
|
|
81
|
+
const bucket = byCategory.get(key);
|
|
82
|
+
if (bucket) {
|
|
83
|
+
bucket.push(score);
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
byCategory.set(key, [score]);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
const categoryAverages = [];
|
|
90
|
+
for (const category of categories) {
|
|
91
|
+
const countable = collectCountableScores(byCategory.get(category.id) ?? [], policies);
|
|
92
|
+
const { average, counted, dropped } = averageCategory(countable, category.dropLowest);
|
|
93
|
+
categoryAverages.push({
|
|
94
|
+
categoryId: category.id,
|
|
95
|
+
name: category.name,
|
|
96
|
+
weight: category.weight,
|
|
97
|
+
average,
|
|
98
|
+
countedScores: counted,
|
|
99
|
+
droppedScores: dropped,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
const uncategorized = byCategory.get(null) ?? [];
|
|
103
|
+
if (uncategorized.length > 0) {
|
|
104
|
+
const countable = collectCountableScores(uncategorized, policies);
|
|
105
|
+
const { average, counted, dropped } = averageCategory(countable, DEFAULT_CATEGORY.dropLowest);
|
|
106
|
+
categoryAverages.push({
|
|
107
|
+
categoryId: DEFAULT_CATEGORY.id,
|
|
108
|
+
name: options?.defaultCategoryName ?? "Uncategorized",
|
|
109
|
+
weight: DEFAULT_CATEGORY.weight,
|
|
110
|
+
average,
|
|
111
|
+
countedScores: counted,
|
|
112
|
+
droppedScores: dropped,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
const contributing = categoryAverages.filter((c) => c.average !== null && c.weight > 0);
|
|
116
|
+
const totalWeight = contributing.reduce((sum, c) => sum + c.weight, 0);
|
|
117
|
+
const percentage = totalWeight > 0
|
|
118
|
+
? roundPercentage(contributing.reduce((sum, c) => sum + c.average * c.weight, 0) / totalWeight, policies.roundingMode)
|
|
119
|
+
: null;
|
|
120
|
+
return { percentage, categories: categoryAverages };
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Maps a percentage to a letter grade using a GradingBoundary `structured`
|
|
124
|
+
* JSON payload (`{ boundaries: [{ grade, minPercentage, maxPercentage }] }`).
|
|
125
|
+
* Returns null when the payload is unparseable or nothing matches.
|
|
126
|
+
*/
|
|
127
|
+
export function resolveLetterGrade(structured, percentage) {
|
|
128
|
+
let boundaries;
|
|
129
|
+
try {
|
|
130
|
+
const parsed = JSON.parse(structured);
|
|
131
|
+
if (!Array.isArray(parsed.boundaries))
|
|
132
|
+
return null;
|
|
133
|
+
boundaries = parsed.boundaries;
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
const sorted = [...boundaries].sort((a, b) => b.minPercentage - a.minPercentage);
|
|
139
|
+
for (const boundary of sorted) {
|
|
140
|
+
if (typeof boundary.minPercentage === "number" &&
|
|
141
|
+
percentage >= boundary.minPercentage) {
|
|
142
|
+
return boundary.grade ?? null;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
//# sourceMappingURL=gradeCalculation.js.map
|
|
148
|
+
//# debugId=f05b8c2d-15ff-58b1-b01e-d40dba432326
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gradeCalculation.js","sources":["services/gradeCalculation.ts"],"sourceRoot":"/","sourcesContent":["/**\n * Grade calculation – pure functions for computing final class grades from\n * categories, weights, and grade-calculation policies. No I/O; unit-testable.\n *\n * Future policies (standards-based grading, etc.) should extend\n * `GradeCalculationPolicies` and be handled inside `computeFinalGrade` so\n * callers never need to change.\n */\n\nexport type GradeRoundingMode = \"NONE\" | \"HALF_UP\" | \"FLOOR\" | \"CEILING\";\n\nexport interface GradeCalculationPolicies {\n /** Count ungraded/missing scores as 0% instead of excluding them. */\n treatMissingAsZero: boolean;\n /** How the final percentage is rounded to a whole number. */\n roundingMode: GradeRoundingMode;\n}\n\nexport interface GradeCategoryInput {\n id: string;\n name: string;\n /** Relative weight of this category toward the final grade. */\n weight: number;\n /** Number of lowest-percentage scores dropped within this category. */\n dropLowest: number;\n}\n\nexport interface ScoreInput {\n /** Category the score belongs to; null falls into the default category. */\n categoryId: string | null;\n /** Points earned; null means missing/ungraded. */\n pointsEarned: number | null;\n /** Maximum points for the item; items with maxPoints <= 0 are ignored. */\n maxPoints: number;\n /** Relative weight of the item within its category (defaults to 1). */\n weight?: number;\n}\n\nexport interface CategoryAverage {\n categoryId: string | null;\n name: string;\n weight: number;\n /** Percentage 0–100, or null when no scores counted. */\n average: number | null;\n countedScores: number;\n droppedScores: number;\n}\n\nexport interface FinalGradeResult {\n /** Final percentage 0–100 (rounded per policy), or null when no data. */\n percentage: number | null;\n categories: CategoryAverage[];\n}\n\n/** Synthetic category used for scores without a category. */\nexport const DEFAULT_CATEGORY = {\n id: null,\n weight: 1,\n dropLowest: 0,\n} as const;\n\nexport function roundPercentage(\n value: number,\n mode: GradeRoundingMode\n): number {\n switch (mode) {\n case \"HALF_UP\":\n return Math.round(value);\n case \"FLOOR\":\n return Math.floor(value);\n case \"CEILING\":\n return Math.ceil(value);\n case \"NONE\":\n default:\n return value;\n }\n}\n\ninterface CountableScore {\n percentage: number;\n weight: number;\n}\n\nfunction collectCountableScores(\n scores: ScoreInput[],\n policies: GradeCalculationPolicies\n): CountableScore[] {\n const countable: CountableScore[] = [];\n for (const score of scores) {\n if (score.maxPoints <= 0) continue;\n if (score.pointsEarned === null) {\n if (!policies.treatMissingAsZero) continue;\n countable.push({ percentage: 0, weight: score.weight ?? 1 });\n continue;\n }\n countable.push({\n percentage: (score.pointsEarned / score.maxPoints) * 100,\n weight: score.weight ?? 1,\n });\n }\n return countable;\n}\n\nfunction averageCategory(\n countable: CountableScore[],\n dropLowest: number\n): { average: number | null; counted: number; dropped: number } {\n // Always keep at least one score so a category never collapses to null\n // just because dropLowest >= score count.\n const dropCount = Math.min(Math.max(dropLowest, 0), countable.length - 1);\n const kept =\n dropCount > 0\n ? [...countable]\n .sort((a, b) => a.percentage - b.percentage)\n .slice(dropCount)\n : countable;\n\n const totalWeight = kept.reduce((sum, s) => sum + s.weight, 0);\n if (kept.length === 0 || totalWeight <= 0) {\n return { average: null, counted: 0, dropped: 0 };\n }\n\n const weighted = kept.reduce((sum, s) => sum + s.percentage * s.weight, 0);\n return {\n average: weighted / totalWeight,\n counted: kept.length,\n dropped: dropCount,\n };\n}\n\n/**\n * Computes the final class grade as the weighted average of category\n * averages. Scores with no category fall into a default category with\n * weight `DEFAULT_CATEGORY.weight`. Categories without counted scores are\n * excluded from the weighted average.\n */\nexport function computeFinalGrade(\n policies: GradeCalculationPolicies,\n categories: GradeCategoryInput[],\n scores: ScoreInput[],\n options?: { defaultCategoryName?: string }\n): FinalGradeResult {\n const knownIds = new Set(categories.map((c) => c.id));\n const byCategory = new Map<string | null, ScoreInput[]>();\n for (const score of scores) {\n const key =\n score.categoryId !== null && knownIds.has(score.categoryId)\n ? score.categoryId\n : null;\n const bucket = byCategory.get(key);\n if (bucket) {\n bucket.push(score);\n } else {\n byCategory.set(key, [score]);\n }\n }\n\n const categoryAverages: CategoryAverage[] = [];\n\n for (const category of categories) {\n const countable = collectCountableScores(\n byCategory.get(category.id) ?? [],\n policies\n );\n const { average, counted, dropped } = averageCategory(\n countable,\n category.dropLowest\n );\n categoryAverages.push({\n categoryId: category.id,\n name: category.name,\n weight: category.weight,\n average,\n countedScores: counted,\n droppedScores: dropped,\n });\n }\n\n const uncategorized = byCategory.get(null) ?? [];\n if (uncategorized.length > 0) {\n const countable = collectCountableScores(uncategorized, policies);\n const { average, counted, dropped } = averageCategory(\n countable,\n DEFAULT_CATEGORY.dropLowest\n );\n categoryAverages.push({\n categoryId: DEFAULT_CATEGORY.id,\n name: options?.defaultCategoryName ?? \"Uncategorized\",\n weight: DEFAULT_CATEGORY.weight,\n average,\n countedScores: counted,\n droppedScores: dropped,\n });\n }\n\n const contributing = categoryAverages.filter(\n (c) => c.average !== null && c.weight > 0\n );\n const totalWeight = contributing.reduce((sum, c) => sum + c.weight, 0);\n\n const percentage =\n totalWeight > 0\n ? roundPercentage(\n contributing.reduce(\n (sum, c) => sum + (c.average as number) * c.weight,\n 0\n ) / totalWeight,\n policies.roundingMode\n )\n : null;\n\n return { percentage, categories: categoryAverages };\n}\n\ninterface ParsedBoundary {\n grade: string;\n minPercentage: number;\n maxPercentage: number;\n}\n\n/**\n * Maps a percentage to a letter grade using a GradingBoundary `structured`\n * JSON payload (`{ boundaries: [{ grade, minPercentage, maxPercentage }] }`).\n * Returns null when the payload is unparseable or nothing matches.\n */\nexport function resolveLetterGrade(\n structured: string,\n percentage: number\n): string | null {\n let boundaries: ParsedBoundary[];\n try {\n const parsed = JSON.parse(structured) as {\n boundaries?: ParsedBoundary[];\n };\n if (!Array.isArray(parsed.boundaries)) return null;\n boundaries = parsed.boundaries;\n } catch {\n return null;\n }\n\n const sorted = [...boundaries].sort(\n (a, b) => b.minPercentage - a.minPercentage\n );\n for (const boundary of sorted) {\n if (\n typeof boundary.minPercentage === \"number\" &&\n percentage >= boundary.minPercentage\n ) {\n return boundary.grade ?? null;\n }\n }\n return null;\n}\n"],"names":[],"mappings":"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","debug_id":"f05b8c2d-15ff-58b1-b01e-d40dba432326"}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gradebook service – grade categories, custom columns/entries, class
|
|
3
|
+
* grade-calculation policies, the computed gradebook, and CSV export.
|
|
4
|
+
*
|
|
5
|
+
* All grade math lives in `gradeCalculation.ts` (pure, unit-tested); this
|
|
6
|
+
* service only assembles inputs and persists configuration.
|
|
7
|
+
*/
|
|
8
|
+
import { GradeRoundingMode as PrismaGradeRoundingMode } from "@prisma/client";
|
|
9
|
+
import { type FinalGradeResult } from "./gradeCalculation.js";
|
|
10
|
+
export declare function listGradeCategories(classId: string): Promise<{
|
|
11
|
+
id: string;
|
|
12
|
+
name: string;
|
|
13
|
+
classId: string;
|
|
14
|
+
weight: number;
|
|
15
|
+
dropLowest: number;
|
|
16
|
+
position: number;
|
|
17
|
+
}[]>;
|
|
18
|
+
export declare function createGradeCategory(classId: string, data: {
|
|
19
|
+
name: string;
|
|
20
|
+
weight: number;
|
|
21
|
+
dropLowest?: number;
|
|
22
|
+
position?: number;
|
|
23
|
+
}): Promise<{
|
|
24
|
+
id: string;
|
|
25
|
+
name: string;
|
|
26
|
+
classId: string;
|
|
27
|
+
weight: number;
|
|
28
|
+
dropLowest: number;
|
|
29
|
+
position: number;
|
|
30
|
+
}>;
|
|
31
|
+
export declare function updateGradeCategory(categoryId: string, classId: string, data: {
|
|
32
|
+
name?: string;
|
|
33
|
+
weight?: number;
|
|
34
|
+
dropLowest?: number;
|
|
35
|
+
position?: number;
|
|
36
|
+
}): Promise<{
|
|
37
|
+
id: string;
|
|
38
|
+
name: string;
|
|
39
|
+
classId: string;
|
|
40
|
+
weight: number;
|
|
41
|
+
dropLowest: number;
|
|
42
|
+
position: number;
|
|
43
|
+
}>;
|
|
44
|
+
export declare function deleteGradeCategory(categoryId: string, classId: string): Promise<{
|
|
45
|
+
id: string;
|
|
46
|
+
name: string;
|
|
47
|
+
classId: string;
|
|
48
|
+
weight: number;
|
|
49
|
+
dropLowest: number;
|
|
50
|
+
position: number;
|
|
51
|
+
}>;
|
|
52
|
+
export declare function assignAssignmentCategory(classId: string, assignmentId: string, gradeCategoryId: string | null): Promise<{
|
|
53
|
+
type: import(".prisma/client").$Enums.AssignmentType;
|
|
54
|
+
id: string;
|
|
55
|
+
createdAt: Date | null;
|
|
56
|
+
classId: string;
|
|
57
|
+
title: string;
|
|
58
|
+
dueDate: Date;
|
|
59
|
+
maxGrade: number | null;
|
|
60
|
+
eventId: string | null;
|
|
61
|
+
instructions: string;
|
|
62
|
+
modifiedAt: Date | null;
|
|
63
|
+
teacherId: string;
|
|
64
|
+
acceptFiles: boolean;
|
|
65
|
+
acceptExtendedResponse: boolean;
|
|
66
|
+
acceptWorksheet: boolean;
|
|
67
|
+
gradeWithAI: boolean;
|
|
68
|
+
aiPolicyLevel: number;
|
|
69
|
+
sectionId: string | null;
|
|
70
|
+
graded: boolean;
|
|
71
|
+
weight: number;
|
|
72
|
+
inProgress: boolean;
|
|
73
|
+
template: boolean;
|
|
74
|
+
markSchemeId: string | null;
|
|
75
|
+
order: number | null;
|
|
76
|
+
gradingBoundaryId: string | null;
|
|
77
|
+
gradeCategoryId: string | null;
|
|
78
|
+
}>;
|
|
79
|
+
export declare function listCustomGradeColumns(classId: string): Promise<({
|
|
80
|
+
entries: {
|
|
81
|
+
id: string;
|
|
82
|
+
points: number;
|
|
83
|
+
studentId: string;
|
|
84
|
+
note: string | null;
|
|
85
|
+
}[];
|
|
86
|
+
} & {
|
|
87
|
+
id: string;
|
|
88
|
+
name: string;
|
|
89
|
+
classId: string;
|
|
90
|
+
gradeCategoryId: string | null;
|
|
91
|
+
position: number;
|
|
92
|
+
maxPoints: number;
|
|
93
|
+
})[]>;
|
|
94
|
+
export declare function createCustomGradeColumn(classId: string, data: {
|
|
95
|
+
name: string;
|
|
96
|
+
maxPoints: number;
|
|
97
|
+
gradeCategoryId?: string | null;
|
|
98
|
+
position?: number;
|
|
99
|
+
}): Promise<{
|
|
100
|
+
id: string;
|
|
101
|
+
name: string;
|
|
102
|
+
classId: string;
|
|
103
|
+
gradeCategoryId: string | null;
|
|
104
|
+
position: number;
|
|
105
|
+
maxPoints: number;
|
|
106
|
+
}>;
|
|
107
|
+
export declare function updateCustomGradeColumn(columnId: string, classId: string, data: {
|
|
108
|
+
name?: string;
|
|
109
|
+
maxPoints?: number;
|
|
110
|
+
gradeCategoryId?: string | null;
|
|
111
|
+
position?: number;
|
|
112
|
+
}): Promise<{
|
|
113
|
+
id: string;
|
|
114
|
+
name: string;
|
|
115
|
+
classId: string;
|
|
116
|
+
gradeCategoryId: string | null;
|
|
117
|
+
position: number;
|
|
118
|
+
maxPoints: number;
|
|
119
|
+
}>;
|
|
120
|
+
export declare function deleteCustomGradeColumn(columnId: string, classId: string): Promise<{
|
|
121
|
+
id: string;
|
|
122
|
+
name: string;
|
|
123
|
+
classId: string;
|
|
124
|
+
gradeCategoryId: string | null;
|
|
125
|
+
position: number;
|
|
126
|
+
maxPoints: number;
|
|
127
|
+
}>;
|
|
128
|
+
export declare function setCustomGradeEntry(classId: string, columnId: string, studentId: string, points: number | null, note?: string | null): Promise<{
|
|
129
|
+
id: string;
|
|
130
|
+
points: number;
|
|
131
|
+
studentId: string;
|
|
132
|
+
columnId: string;
|
|
133
|
+
note: string | null;
|
|
134
|
+
} | null>;
|
|
135
|
+
export declare function getGradePolicies(classId: string): Promise<{
|
|
136
|
+
id: string;
|
|
137
|
+
treatMissingAsZero: boolean;
|
|
138
|
+
gradeRoundingMode: import(".prisma/client").$Enums.GradeRoundingMode;
|
|
139
|
+
}>;
|
|
140
|
+
export declare function updateGradePolicies(classId: string, data: {
|
|
141
|
+
treatMissingAsZero?: boolean;
|
|
142
|
+
gradeRoundingMode?: PrismaGradeRoundingMode;
|
|
143
|
+
}): Promise<{
|
|
144
|
+
id: string;
|
|
145
|
+
treatMissingAsZero: boolean;
|
|
146
|
+
gradeRoundingMode: import(".prisma/client").$Enums.GradeRoundingMode;
|
|
147
|
+
}>;
|
|
148
|
+
export interface GradebookStudentRow {
|
|
149
|
+
studentId: string;
|
|
150
|
+
studentName: string;
|
|
151
|
+
assignmentScores: Record<string, number | null>;
|
|
152
|
+
customScores: Record<string, {
|
|
153
|
+
points: number | null;
|
|
154
|
+
note: string | null;
|
|
155
|
+
}>;
|
|
156
|
+
result: FinalGradeResult;
|
|
157
|
+
letterGrade: string | null;
|
|
158
|
+
}
|
|
159
|
+
export declare function getComputedGradebook(classId: string, userId: string): Promise<{
|
|
160
|
+
policies: {
|
|
161
|
+
treatMissingAsZero: boolean;
|
|
162
|
+
roundingMode: import(".prisma/client").$Enums.GradeRoundingMode;
|
|
163
|
+
};
|
|
164
|
+
categories: {
|
|
165
|
+
id: string;
|
|
166
|
+
name: string;
|
|
167
|
+
classId: string;
|
|
168
|
+
weight: number;
|
|
169
|
+
dropLowest: number;
|
|
170
|
+
position: number;
|
|
171
|
+
}[];
|
|
172
|
+
assignments: {
|
|
173
|
+
id: string;
|
|
174
|
+
title: string;
|
|
175
|
+
maxGrade: number | null;
|
|
176
|
+
weight: number;
|
|
177
|
+
gradeCategoryId: string | null;
|
|
178
|
+
dueDate: Date;
|
|
179
|
+
}[];
|
|
180
|
+
customColumns: {
|
|
181
|
+
id: string;
|
|
182
|
+
name: string;
|
|
183
|
+
maxPoints: number;
|
|
184
|
+
gradeCategoryId: string | null;
|
|
185
|
+
position: number;
|
|
186
|
+
}[];
|
|
187
|
+
gradingBoundary: {
|
|
188
|
+
id: string;
|
|
189
|
+
name: string | null;
|
|
190
|
+
structured: string;
|
|
191
|
+
};
|
|
192
|
+
students: GradebookStudentRow[];
|
|
193
|
+
}>;
|
|
194
|
+
export declare function exportGradebookCsv(classId: string, userId: string): Promise<{
|
|
195
|
+
csv: string;
|
|
196
|
+
}>;
|
|
197
|
+
//# sourceMappingURL=gradebook.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gradebook.d.ts","sourceRoot":"/","sources":["services/gradebook.ts"],"names":[],"mappings":"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"}
|