完整性更新
现在已经实现了大部分基础功能
This commit is contained in:
@@ -1,10 +1,11 @@
|
||||
import "server-only"
|
||||
|
||||
import { cache } from "react"
|
||||
import { and, count, desc, eq, inArray, isNull, lte, or } from "drizzle-orm"
|
||||
import { and, count, desc, eq, inArray, isNull, lte, or, sql } from "drizzle-orm"
|
||||
|
||||
import { db } from "@/shared/db"
|
||||
import {
|
||||
classEnrollments,
|
||||
homeworkAnswers,
|
||||
homeworkAssignmentQuestions,
|
||||
homeworkAssignmentTargets,
|
||||
@@ -15,13 +16,19 @@ import {
|
||||
|
||||
import type {
|
||||
HomeworkAssignmentListItem,
|
||||
HomeworkAssignmentReviewListItem,
|
||||
HomeworkQuestionContent,
|
||||
HomeworkAssignmentStatus,
|
||||
HomeworkSubmissionDetails,
|
||||
HomeworkSubmissionListItem,
|
||||
HomeworkAssignmentAnalytics,
|
||||
HomeworkAssignmentQuestionAnalytics,
|
||||
StudentHomeworkAssignmentListItem,
|
||||
StudentHomeworkProgressStatus,
|
||||
StudentHomeworkTakeData,
|
||||
StudentDashboardGradeProps,
|
||||
StudentHomeworkScoreAnalytics,
|
||||
StudentRanking,
|
||||
} from "./types"
|
||||
|
||||
const isRecord = (v: unknown): v is Record<string, unknown> => typeof v === "object" && v !== null
|
||||
@@ -31,11 +38,42 @@ const toQuestionContent = (v: unknown): HomeworkQuestionContent | null => {
|
||||
return v as HomeworkQuestionContent
|
||||
}
|
||||
|
||||
export const getHomeworkAssignments = cache(async (params?: { creatorId?: string; ids?: string[] }) => {
|
||||
const getAssignmentMaxScoreById = async (assignmentIds: string[]): Promise<Map<string, number>> => {
|
||||
const ids = assignmentIds.filter((v) => v.trim().length > 0)
|
||||
if (ids.length === 0) return new Map()
|
||||
|
||||
const rows = await db
|
||||
.select({
|
||||
assignmentId: homeworkAssignmentQuestions.assignmentId,
|
||||
maxScore: sql<number>`COALESCE(SUM(${homeworkAssignmentQuestions.score}), 0)`,
|
||||
})
|
||||
.from(homeworkAssignmentQuestions)
|
||||
.where(inArray(homeworkAssignmentQuestions.assignmentId, ids))
|
||||
.groupBy(homeworkAssignmentQuestions.assignmentId)
|
||||
|
||||
const map = new Map<string, number>()
|
||||
for (const r of rows) map.set(r.assignmentId, Number(r.maxScore ?? 0))
|
||||
return map
|
||||
}
|
||||
|
||||
export const getHomeworkAssignments = cache(async (params?: { creatorId?: string; ids?: string[]; classId?: string }) => {
|
||||
const conditions = []
|
||||
|
||||
if (params?.creatorId) conditions.push(eq(homeworkAssignments.creatorId, params.creatorId))
|
||||
if (params?.ids && params.ids.length > 0) conditions.push(inArray(homeworkAssignments.id, params.ids))
|
||||
if (params?.classId) {
|
||||
const classStudentIds = db
|
||||
.select({ studentId: classEnrollments.studentId })
|
||||
.from(classEnrollments)
|
||||
.where(eq(classEnrollments.classId, params.classId))
|
||||
|
||||
const targetAssignmentIds = db
|
||||
.selectDistinct({ assignmentId: homeworkAssignmentTargets.assignmentId })
|
||||
.from(homeworkAssignmentTargets)
|
||||
.where(inArray(homeworkAssignmentTargets.studentId, classStudentIds))
|
||||
|
||||
conditions.push(inArray(homeworkAssignments.id, targetAssignmentIds))
|
||||
}
|
||||
|
||||
const data = await db.query.homeworkAssignments.findMany({
|
||||
where: conditions.length ? and(...conditions) : undefined,
|
||||
@@ -64,9 +102,102 @@ export const getHomeworkAssignments = cache(async (params?: { creatorId?: string
|
||||
})
|
||||
})
|
||||
|
||||
export const getHomeworkSubmissions = cache(async (params?: { assignmentId?: string }) => {
|
||||
export const getHomeworkAssignmentReviewList = cache(async (params: { creatorId: string }) => {
|
||||
const creatorId = params.creatorId.trim()
|
||||
if (!creatorId) return []
|
||||
|
||||
const assignments = await db.query.homeworkAssignments.findMany({
|
||||
where: eq(homeworkAssignments.creatorId, creatorId),
|
||||
orderBy: [desc(homeworkAssignments.createdAt)],
|
||||
with: { sourceExam: true },
|
||||
})
|
||||
|
||||
if (assignments.length === 0) return []
|
||||
|
||||
const assignmentIds = assignments.map((a) => a.id)
|
||||
|
||||
const targetCountRows = await db
|
||||
.select({
|
||||
assignmentId: homeworkAssignmentTargets.assignmentId,
|
||||
targetCount: sql<number>`COUNT(*)`,
|
||||
})
|
||||
.from(homeworkAssignmentTargets)
|
||||
.where(inArray(homeworkAssignmentTargets.assignmentId, assignmentIds))
|
||||
.groupBy(homeworkAssignmentTargets.assignmentId)
|
||||
|
||||
const targetCountByAssignmentId = new Map<string, number>()
|
||||
for (const r of targetCountRows) targetCountByAssignmentId.set(r.assignmentId, Number(r.targetCount ?? 0))
|
||||
|
||||
const submittedCountRows = await db
|
||||
.select({
|
||||
assignmentId: homeworkSubmissions.assignmentId,
|
||||
submittedCount: sql<number>`COUNT(DISTINCT ${homeworkSubmissions.studentId})`,
|
||||
})
|
||||
.from(homeworkSubmissions)
|
||||
.where(
|
||||
and(
|
||||
inArray(homeworkSubmissions.assignmentId, assignmentIds),
|
||||
inArray(homeworkSubmissions.status, ["submitted", "graded"])
|
||||
)
|
||||
)
|
||||
.groupBy(homeworkSubmissions.assignmentId)
|
||||
|
||||
const submittedCountByAssignmentId = new Map<string, number>()
|
||||
for (const r of submittedCountRows) submittedCountByAssignmentId.set(r.assignmentId, Number(r.submittedCount ?? 0))
|
||||
|
||||
const gradedCountRows = await db
|
||||
.select({
|
||||
assignmentId: homeworkSubmissions.assignmentId,
|
||||
gradedCount: sql<number>`COUNT(DISTINCT ${homeworkSubmissions.studentId})`,
|
||||
})
|
||||
.from(homeworkSubmissions)
|
||||
.where(and(inArray(homeworkSubmissions.assignmentId, assignmentIds), eq(homeworkSubmissions.status, "graded")))
|
||||
.groupBy(homeworkSubmissions.assignmentId)
|
||||
|
||||
const gradedCountByAssignmentId = new Map<string, number>()
|
||||
for (const r of gradedCountRows) gradedCountByAssignmentId.set(r.assignmentId, Number(r.gradedCount ?? 0))
|
||||
|
||||
return assignments.map((a) => {
|
||||
const item: HomeworkAssignmentReviewListItem = {
|
||||
id: a.id,
|
||||
title: a.title,
|
||||
status: (a.status as HomeworkAssignmentReviewListItem["status"]) ?? "draft",
|
||||
sourceExamTitle: a.sourceExam.title,
|
||||
dueAt: a.dueAt ? a.dueAt.toISOString() : null,
|
||||
targetCount: targetCountByAssignmentId.get(a.id) ?? 0,
|
||||
submittedCount: submittedCountByAssignmentId.get(a.id) ?? 0,
|
||||
gradedCount: gradedCountByAssignmentId.get(a.id) ?? 0,
|
||||
createdAt: a.createdAt.toISOString(),
|
||||
}
|
||||
return item
|
||||
})
|
||||
})
|
||||
|
||||
export const getHomeworkSubmissions = cache(async (params?: { assignmentId?: string; classId?: string; creatorId?: string }) => {
|
||||
const conditions = []
|
||||
if (params?.assignmentId) conditions.push(eq(homeworkSubmissions.assignmentId, params.assignmentId))
|
||||
if (params?.classId) {
|
||||
const classStudentIds = db
|
||||
.select({ studentId: classEnrollments.studentId })
|
||||
.from(classEnrollments)
|
||||
.where(eq(classEnrollments.classId, params.classId))
|
||||
|
||||
const targetAssignmentIds = db
|
||||
.selectDistinct({ assignmentId: homeworkAssignmentTargets.assignmentId })
|
||||
.from(homeworkAssignmentTargets)
|
||||
.where(inArray(homeworkAssignmentTargets.studentId, classStudentIds))
|
||||
|
||||
conditions.push(inArray(homeworkSubmissions.studentId, classStudentIds))
|
||||
conditions.push(inArray(homeworkSubmissions.assignmentId, targetAssignmentIds))
|
||||
}
|
||||
if (params?.creatorId) {
|
||||
const creatorAssignmentIds = db
|
||||
.select({ assignmentId: homeworkAssignments.id })
|
||||
.from(homeworkAssignments)
|
||||
.where(eq(homeworkAssignments.creatorId, params.creatorId))
|
||||
|
||||
conditions.push(inArray(homeworkSubmissions.assignmentId, creatorAssignmentIds))
|
||||
}
|
||||
|
||||
const data = await db.query.homeworkSubmissions.findMany({
|
||||
where: conditions.length ? and(...conditions) : undefined,
|
||||
@@ -112,6 +243,18 @@ export const getHomeworkAssignmentById = cache(async (id: string) => {
|
||||
.from(homeworkSubmissions)
|
||||
.where(eq(homeworkSubmissions.assignmentId, id))
|
||||
|
||||
const [submittedRow] = await db
|
||||
.select({ c: sql<number>`COUNT(DISTINCT ${homeworkSubmissions.studentId})` })
|
||||
.from(homeworkSubmissions)
|
||||
.where(
|
||||
and(eq(homeworkSubmissions.assignmentId, id), inArray(homeworkSubmissions.status, ["submitted", "graded"]))
|
||||
)
|
||||
|
||||
const [gradedRow] = await db
|
||||
.select({ c: sql<number>`COUNT(DISTINCT ${homeworkSubmissions.studentId})` })
|
||||
.from(homeworkSubmissions)
|
||||
.where(and(eq(homeworkSubmissions.assignmentId, id), eq(homeworkSubmissions.status, "graded")))
|
||||
|
||||
return {
|
||||
id: assignment.id,
|
||||
title: assignment.title,
|
||||
@@ -119,6 +262,7 @@ export const getHomeworkAssignmentById = cache(async (id: string) => {
|
||||
status: (assignment.status as HomeworkAssignmentStatus) ?? "draft",
|
||||
sourceExamId: assignment.sourceExamId,
|
||||
sourceExamTitle: assignment.sourceExam.title,
|
||||
structure: assignment.structure as unknown,
|
||||
availableAt: assignment.availableAt ? assignment.availableAt.toISOString() : null,
|
||||
dueAt: assignment.dueAt ? assignment.dueAt.toISOString() : null,
|
||||
allowLate: assignment.allowLate,
|
||||
@@ -126,11 +270,159 @@ export const getHomeworkAssignmentById = cache(async (id: string) => {
|
||||
maxAttempts: assignment.maxAttempts,
|
||||
targetCount: targetsRow?.c ?? 0,
|
||||
submissionCount: submissionsRow?.c ?? 0,
|
||||
submittedCount: submittedRow?.c ?? 0,
|
||||
gradedCount: gradedRow?.c ?? 0,
|
||||
createdAt: assignment.createdAt.toISOString(),
|
||||
updatedAt: assignment.updatedAt.toISOString(),
|
||||
}
|
||||
})
|
||||
|
||||
export const getHomeworkAssignmentAnalytics = cache(
|
||||
async (assignmentId: string): Promise<HomeworkAssignmentAnalytics | null> => {
|
||||
const assignment = await db.query.homeworkAssignments.findFirst({
|
||||
where: eq(homeworkAssignments.id, assignmentId),
|
||||
with: {
|
||||
sourceExam: true,
|
||||
},
|
||||
})
|
||||
|
||||
if (!assignment) return null
|
||||
|
||||
const [targetsRow] = await db
|
||||
.select({ c: count() })
|
||||
.from(homeworkAssignmentTargets)
|
||||
.where(eq(homeworkAssignmentTargets.assignmentId, assignmentId))
|
||||
|
||||
const [submissionsRow] = await db
|
||||
.select({ c: count() })
|
||||
.from(homeworkSubmissions)
|
||||
.where(eq(homeworkSubmissions.assignmentId, assignmentId))
|
||||
|
||||
const [submittedRow] = await db
|
||||
.select({ c: sql<number>`COUNT(DISTINCT ${homeworkSubmissions.studentId})` })
|
||||
.from(homeworkSubmissions)
|
||||
.where(
|
||||
and(
|
||||
eq(homeworkSubmissions.assignmentId, assignmentId),
|
||||
inArray(homeworkSubmissions.status, ["submitted", "graded"])
|
||||
)
|
||||
)
|
||||
|
||||
const [gradedRow] = await db
|
||||
.select({ c: sql<number>`COUNT(DISTINCT ${homeworkSubmissions.studentId})` })
|
||||
.from(homeworkSubmissions)
|
||||
.where(and(eq(homeworkSubmissions.assignmentId, assignmentId), eq(homeworkSubmissions.status, "graded")))
|
||||
|
||||
const assignmentQuestions = await db.query.homeworkAssignmentQuestions.findMany({
|
||||
where: eq(homeworkAssignmentQuestions.assignmentId, assignmentId),
|
||||
with: { question: true },
|
||||
orderBy: (q, { asc }) => [asc(q.order)],
|
||||
})
|
||||
|
||||
const statsByQuestionId = new Map<string, HomeworkAssignmentQuestionAnalytics>()
|
||||
|
||||
for (const aq of assignmentQuestions) {
|
||||
statsByQuestionId.set(aq.questionId, {
|
||||
questionId: aq.questionId,
|
||||
questionType: aq.question.type,
|
||||
questionContent: toQuestionContent(aq.question.content),
|
||||
maxScore: aq.score ?? 0,
|
||||
order: aq.order ?? 0,
|
||||
errorCount: 0,
|
||||
errorRate: 0,
|
||||
})
|
||||
}
|
||||
|
||||
const gradedSubmissionsAll = await db.query.homeworkSubmissions.findMany({
|
||||
where: and(
|
||||
eq(homeworkSubmissions.assignmentId, assignmentId),
|
||||
eq(homeworkSubmissions.status, "graded")
|
||||
),
|
||||
orderBy: (s, { desc }) => [desc(s.updatedAt)],
|
||||
with: {
|
||||
answers: true,
|
||||
student: true,
|
||||
},
|
||||
})
|
||||
|
||||
const latestByStudentId = new Map<string, (typeof gradedSubmissionsAll)[number]>()
|
||||
for (const s of gradedSubmissionsAll) {
|
||||
if (!latestByStudentId.has(s.studentId)) latestByStudentId.set(s.studentId, s)
|
||||
}
|
||||
const gradedSubmissions = Array.from(latestByStudentId.values())
|
||||
|
||||
const scoreBySubmissionQuestion = new Map<string, number>()
|
||||
const answerBySubmissionQuestion = new Map<string, unknown>()
|
||||
for (const sub of gradedSubmissions) {
|
||||
for (const ans of sub.answers) {
|
||||
const key = `${sub.id}|${ans.questionId}`
|
||||
if (scoreBySubmissionQuestion.has(key)) continue
|
||||
scoreBySubmissionQuestion.set(key, ans.score ?? 0)
|
||||
const raw = ans.answerContent
|
||||
if (isRecord(raw) && "answer" in raw) {
|
||||
answerBySubmissionQuestion.set(key, raw.answer)
|
||||
} else {
|
||||
answerBySubmissionQuestion.set(key, raw)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const denom = gradedSubmissions.length
|
||||
if (denom > 0) {
|
||||
for (const q of statsByQuestionId.values()) {
|
||||
if (q.maxScore <= 0) continue
|
||||
let errors = 0
|
||||
const wrongAnswers: Array<{ studentId: string; studentName: string; answerContent: unknown }> = []
|
||||
for (const sub of gradedSubmissions) {
|
||||
const key = `${sub.id}|${q.questionId}`
|
||||
const score = scoreBySubmissionQuestion.get(key) ?? 0
|
||||
if (score < q.maxScore) {
|
||||
errors += 1
|
||||
wrongAnswers.push({
|
||||
studentId: sub.studentId,
|
||||
studentName: sub.student.name || "Unknown",
|
||||
answerContent: answerBySubmissionQuestion.get(key),
|
||||
})
|
||||
}
|
||||
}
|
||||
q.errorCount = errors
|
||||
q.errorRate = errors / denom
|
||||
q.wrongAnswers = wrongAnswers.slice(0, 500)
|
||||
}
|
||||
}
|
||||
|
||||
const questions: HomeworkAssignmentQuestionAnalytics[] = Array.from(statsByQuestionId.values())
|
||||
.sort((a, b) => a.order - b.order)
|
||||
|
||||
const analytics: HomeworkAssignmentAnalytics = {
|
||||
assignment: {
|
||||
id: assignment.id,
|
||||
title: assignment.title,
|
||||
description: assignment.description,
|
||||
status: (assignment.status as HomeworkAssignmentStatus) ?? "draft",
|
||||
sourceExamId: assignment.sourceExamId,
|
||||
sourceExamTitle: assignment.sourceExam.title,
|
||||
structure: assignment.structure as unknown,
|
||||
availableAt: assignment.availableAt ? assignment.availableAt.toISOString() : null,
|
||||
dueAt: assignment.dueAt ? assignment.dueAt.toISOString() : null,
|
||||
allowLate: assignment.allowLate,
|
||||
lateDueAt: assignment.lateDueAt ? assignment.lateDueAt.toISOString() : null,
|
||||
maxAttempts: assignment.maxAttempts,
|
||||
targetCount: targetsRow?.c ?? 0,
|
||||
submissionCount: submissionsRow?.c ?? 0,
|
||||
submittedCount: submittedRow?.c ?? 0,
|
||||
gradedCount: gradedRow?.c ?? 0,
|
||||
createdAt: assignment.createdAt.toISOString(),
|
||||
updatedAt: assignment.updatedAt.toISOString(),
|
||||
},
|
||||
gradedSampleCount: gradedSubmissions.length,
|
||||
questions,
|
||||
}
|
||||
|
||||
return analytics
|
||||
}
|
||||
)
|
||||
|
||||
export const getHomeworkSubmissionDetails = cache(async (submissionId: string): Promise<HomeworkSubmissionDetails | null> => {
|
||||
const submission = await db.query.homeworkSubmissions.findFirst({
|
||||
where: eq(homeworkSubmissions.id, submissionId),
|
||||
@@ -332,3 +624,158 @@ export const getStudentHomeworkTakeData = cache(async (assignmentId: string, stu
|
||||
})),
|
||||
}
|
||||
})
|
||||
|
||||
export const getStudentDashboardGrades = cache(async (studentId: string): Promise<StudentDashboardGradeProps> => {
|
||||
const id = studentId.trim()
|
||||
if (!id) return { trend: [], recent: [], ranking: null }
|
||||
|
||||
const targetAssignmentIdsRows = await db
|
||||
.select({ assignmentId: homeworkAssignmentTargets.assignmentId })
|
||||
.from(homeworkAssignmentTargets)
|
||||
.where(eq(homeworkAssignmentTargets.studentId, id))
|
||||
|
||||
const targetAssignmentIds = Array.from(new Set(targetAssignmentIdsRows.map((r) => r.assignmentId)))
|
||||
if (targetAssignmentIds.length === 0) return { trend: [], recent: [], ranking: null }
|
||||
|
||||
const gradedSubmissions = await db.query.homeworkSubmissions.findMany({
|
||||
where: and(
|
||||
eq(homeworkSubmissions.studentId, id),
|
||||
inArray(homeworkSubmissions.assignmentId, targetAssignmentIds),
|
||||
eq(homeworkSubmissions.status, "graded")
|
||||
),
|
||||
orderBy: (s, { desc }) => [desc(s.updatedAt)],
|
||||
limit: 200,
|
||||
})
|
||||
|
||||
const latestByAssignmentId = new Map<string, (typeof gradedSubmissions)[number]>()
|
||||
for (const s of gradedSubmissions) {
|
||||
if (!latestByAssignmentId.has(s.assignmentId)) latestByAssignmentId.set(s.assignmentId, s)
|
||||
}
|
||||
|
||||
const unique = Array.from(latestByAssignmentId.values()).sort((a, b) => {
|
||||
const aTime = (a.submittedAt ?? a.updatedAt).getTime()
|
||||
const bTime = (b.submittedAt ?? b.updatedAt).getTime()
|
||||
return aTime - bTime
|
||||
})
|
||||
|
||||
const trendSubmissions = unique.slice(-10)
|
||||
const recentSubmissions = [...trendSubmissions].sort((a, b) => {
|
||||
const aTime = (a.submittedAt ?? a.updatedAt).getTime()
|
||||
const bTime = (b.submittedAt ?? b.updatedAt).getTime()
|
||||
return bTime - aTime
|
||||
})
|
||||
|
||||
const assignmentIds = Array.from(new Set(trendSubmissions.map((s) => s.assignmentId)))
|
||||
const assignments = await db.query.homeworkAssignments.findMany({
|
||||
where: inArray(homeworkAssignments.id, assignmentIds),
|
||||
})
|
||||
const titleByAssignmentId = new Map(assignments.map((a) => [a.id, a.title] as const))
|
||||
const maxScoreByAssignmentId = await getAssignmentMaxScoreById(assignmentIds)
|
||||
|
||||
const toAnalytics = (s: (typeof trendSubmissions)[number]): StudentHomeworkScoreAnalytics => {
|
||||
const maxScore = maxScoreByAssignmentId.get(s.assignmentId) ?? 0
|
||||
const score = s.score ?? 0
|
||||
const percentage = maxScore > 0 ? (score / maxScore) * 100 : 0
|
||||
return {
|
||||
assignmentId: s.assignmentId,
|
||||
assignmentTitle: titleByAssignmentId.get(s.assignmentId) ?? "Untitled",
|
||||
score,
|
||||
maxScore,
|
||||
percentage,
|
||||
submittedAt: (s.submittedAt ?? s.updatedAt).toISOString(),
|
||||
}
|
||||
}
|
||||
|
||||
const trend = trendSubmissions.map(toAnalytics)
|
||||
const recent = recentSubmissions.map(toAnalytics).slice(0, 5)
|
||||
|
||||
const enrollment = await db.query.classEnrollments.findFirst({
|
||||
where: and(eq(classEnrollments.studentId, id), eq(classEnrollments.status, "active")),
|
||||
orderBy: (e, { asc }) => [asc(e.createdAt)],
|
||||
})
|
||||
|
||||
if (!enrollment) return { trend, recent, ranking: null }
|
||||
|
||||
const classStudents = await db
|
||||
.select({ studentId: classEnrollments.studentId })
|
||||
.from(classEnrollments)
|
||||
.where(and(eq(classEnrollments.classId, enrollment.classId), eq(classEnrollments.status, "active")))
|
||||
|
||||
const classStudentIds = Array.from(new Set(classStudents.map((r) => r.studentId)))
|
||||
const classSize = classStudentIds.length
|
||||
if (classSize === 0) return { trend, recent, ranking: null }
|
||||
|
||||
const classAssignmentIdsRows = await db
|
||||
.selectDistinct({ assignmentId: homeworkAssignmentTargets.assignmentId })
|
||||
.from(homeworkAssignmentTargets)
|
||||
.where(inArray(homeworkAssignmentTargets.studentId, classStudentIds))
|
||||
|
||||
const classAssignmentIds = Array.from(new Set(classAssignmentIdsRows.map((r) => r.assignmentId)))
|
||||
if (classAssignmentIds.length === 0) return { trend, recent, ranking: null }
|
||||
|
||||
const classMaxScoreByAssignmentId = await getAssignmentMaxScoreById(classAssignmentIds)
|
||||
|
||||
const classGradedSubmissions = await db.query.homeworkSubmissions.findMany({
|
||||
where: and(
|
||||
inArray(homeworkSubmissions.studentId, classStudentIds),
|
||||
inArray(homeworkSubmissions.assignmentId, classAssignmentIds),
|
||||
eq(homeworkSubmissions.status, "graded")
|
||||
),
|
||||
orderBy: (s, { desc }) => [desc(s.updatedAt)],
|
||||
limit: 5000,
|
||||
})
|
||||
|
||||
const latestByStudentAssignment = new Map<string, (typeof classGradedSubmissions)[number]>()
|
||||
for (const s of classGradedSubmissions) {
|
||||
const key = `${s.studentId}|${s.assignmentId}`
|
||||
if (!latestByStudentAssignment.has(key)) latestByStudentAssignment.set(key, s)
|
||||
}
|
||||
|
||||
const totalsByStudentId = new Map<string, { score: number; maxScore: number }>()
|
||||
for (const sub of latestByStudentAssignment.values()) {
|
||||
const maxScore = classMaxScoreByAssignmentId.get(sub.assignmentId) ?? 0
|
||||
const score = sub.score ?? 0
|
||||
const prev = totalsByStudentId.get(sub.studentId) ?? { score: 0, maxScore: 0 }
|
||||
totalsByStudentId.set(sub.studentId, {
|
||||
score: prev.score + score,
|
||||
maxScore: prev.maxScore + maxScore,
|
||||
})
|
||||
}
|
||||
|
||||
const classUsers = await db
|
||||
.select({ id: users.id, name: users.name })
|
||||
.from(users)
|
||||
.where(inArray(users.id, classStudentIds))
|
||||
|
||||
const nameByStudentId = new Map(classUsers.map((u) => [u.id, u.name ?? "Student"] as const))
|
||||
const myName = nameByStudentId.get(id) ?? "Student"
|
||||
|
||||
const ranked = classStudentIds
|
||||
.map((studentId) => {
|
||||
const totals = totalsByStudentId.get(studentId) ?? { score: 0, maxScore: 0 }
|
||||
const percentage = totals.maxScore > 0 ? (totals.score / totals.maxScore) * 100 : 0
|
||||
return { studentId, percentage, totals }
|
||||
})
|
||||
.sort((a, b) => {
|
||||
if (b.percentage !== a.percentage) return b.percentage - a.percentage
|
||||
return a.studentId.localeCompare(b.studentId)
|
||||
})
|
||||
|
||||
const myIndex = ranked.findIndex((r) => r.studentId === id)
|
||||
if (myIndex < 0) return { trend, recent, ranking: null }
|
||||
|
||||
const myTotals = ranked[myIndex]?.totals ?? { score: 0, maxScore: 0 }
|
||||
const myPercentage = myTotals.maxScore > 0 ? (myTotals.score / myTotals.maxScore) * 100 : 0
|
||||
|
||||
const ranking: StudentRanking = {
|
||||
studentId: id,
|
||||
studentName: myName,
|
||||
rank: myIndex + 1,
|
||||
classSize,
|
||||
totalScore: myTotals.score,
|
||||
totalMaxScore: myTotals.maxScore,
|
||||
percentage: myPercentage,
|
||||
}
|
||||
|
||||
return { trend, recent, ranking }
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user