update
Deploy / deploy (push) Canceled after 0s

This commit is contained in:
xuyue committed 2026-09-27 19:36:28 +08:00
1 parent 23844ae301
commit d25b5dbe6d
268 files changed
+3684 -8861

No files matched your search

+24 -74
View File
@@ -47,27 +47,20 @@ async function loadClassUsers(classNames?: string[], gradePrefix?: string) {
submissionNumber: schema.userProfile.submissionNumber,
})
.from(schema.user)
.innerJoin(
schema.userProfile,
eq(schema.userProfile.userId, schema.user.id),
)
.innerJoin(schema.userProfile, eq(schema.userProfile.userId, schema.user.id))
.where(and(...filters))
return rows.filter((row): row is ClassUser => row.className !== null)
}
function mean(values: number[]) {
return values.length
? values.reduce((sum, value) => sum + value, 0) / values.length
: 0
return values.length ? values.reduce((sum, value) => sum + value, 0) / values.length : 0
}
function median(values: number[]) {
if (!values.length) return 0
const sorted = [...values].sort((a, b) => a - b)
const middle = Math.floor(sorted.length / 2)
return sorted.length % 2
? sorted[middle]!
: (sorted[middle - 1]! + sorted[middle]!) / 2
return sorted.length % 2 ? sorted[middle]! : (sorted[middle - 1]! + sorted[middle]!) / 2
}
function quantile(values: number[], p: number) {
@@ -85,58 +78,42 @@ function sampleStdDev(values: number[]) {
if (values.length <= 1) return 0
const average = mean(values)
return Math.sqrt(
values.reduce((sum, value) => sum + (value - average) ** 2, 0) /
(values.length - 1),
values.reduce((sum, value) => sum + (value - average) ** 2, 0) / (values.length - 1),
)
}
classroomRoutes.get("/rankings/classes", async (c) => {
const grade = c.req.query("grade")?.trim()
if (!grade || !/^\d+$/.test(grade))
return failure(c, 400, "invalid-grade", "grade is required")
if (!grade || !/^\d+$/.test(grade)) return failure(c, 400, "invalid-grade", "grade is required")
const users = await loadClassUsers(undefined, grade)
const groups = new Map<string, ClassUser[]>()
for (const user of users)
groups.set(user.className, [...(groups.get(user.className) ?? []), user])
const result = [...groups]
.map(([className, members]) => {
const totalAc = members.reduce(
(sum, member) => sum + member.acceptedNumber,
0,
)
const totalSubmission = members.reduce(
(sum, member) => sum + member.submissionNumber,
0,
)
const totalAc = members.reduce((sum, member) => sum + member.acceptedNumber, 0)
const totalSubmission = members.reduce((sum, member) => sum + member.submissionNumber, 0)
return {
className,
userCount: members.length,
totalAc,
totalSubmission,
avgAc: rounded(totalAc / members.length),
acRate:
totalSubmission > 0 ? rounded((totalAc / totalSubmission) * 100) : 0,
acRate: totalSubmission > 0 ? rounded((totalAc / totalSubmission) * 100) : 0,
}
})
.sort(
(a, b) => b.totalAc - a.totalAc || a.totalSubmission - b.totalSubmission,
)
.sort((a, b) => b.totalAc - a.totalAc || a.totalSubmission - b.totalSubmission)
return success(
c,
result.map(
(item, index) => ({ ...item, rank: index + 1 }) satisfies ClassRankItem,
),
result.map((item, index) => ({ ...item, rank: index + 1 }) satisfies ClassRankItem),
)
})
classroomRoutes.get("/me/class-rank", requireAuth, async (c) => {
const user = c.get("user")!
if (!user.className)
return failure(c, 400, "class-missing", "用户没有班级信息")
if (!user.className) return failure(c, 400, "class-missing", "用户没有班级信息")
const members = (await loadClassUsers([user.className])).sort(
(a, b) =>
b.acceptedNumber - a.acceptedNumber ||
a.submissionNumber - b.submissionNumber,
(a, b) => b.acceptedNumber - a.acceptedNumber || a.submissionNumber - b.submissionNumber,
)
const ranks = members.map((member, index) => ({
userId: member.userId,
@@ -165,11 +142,7 @@ classroomRoutes.get("/me/class-rank", requireAuth, async (c) => {
})
classroomRoutes.post("/classes/comparison", async (c) => {
const parsed = await parseBody(
c,
classComparisonRequestSchema,
"At least one class is required",
)
const parsed = await parseBody(c, classComparisonRequestSchema, "At least one class is required")
if (!parsed.success) return parsed.response
const users = await loadClassUsers(parsed.data.classNames)
const allAc = users.map((user) => user.acceptedNumber)
@@ -200,21 +173,12 @@ classroomRoutes.post("/classes/comparison", async (c) => {
lte(schema.submission.createTime, parsed.data.endTime!),
),
)
const userClass = new Map(
users.map((user) => [user.userId, user.className]),
)
const userClass = new Map(users.map((user) => [user.userId, user.className]))
for (const row of rows) {
const className = userClass.get(row.userId)
if (!className) continue
recentSubmissionCount.set(
className,
(recentSubmissionCount.get(className) ?? 0) + 1,
)
if (
[JudgeStatus.ACCEPTED, JudgeStatus.AST_CHECK_FAILED].includes(
row.result as 0 | 10,
)
) {
recentSubmissionCount.set(className, (recentSubmissionCount.get(className) ?? 0) + 1)
if ([JudgeStatus.ACCEPTED, JudgeStatus.AST_CHECK_FAILED].includes(row.result as 0 | 10)) {
const set = recentByUser.get(row.userId) ?? new Set<number>()
set.add(row.problemId)
recentByUser.set(row.userId, set)
@@ -223,17 +187,12 @@ classroomRoutes.post("/classes/comparison", async (c) => {
}
const comparisons = [...byClass].map(([className, members]) => {
const ac = members
.map((member) => member.acceptedNumber)
.sort((a, b) => b - a)
const submissions = members
.map((member) => member.submissionNumber)
.sort((a, b) => b - a)
const ac = members.map((member) => member.acceptedNumber).sort((a, b) => b - a)
const submissions = members.map((member) => member.submissionNumber).sort((a, b) => b - a)
const userCount = members.length
const topCount = Math.max(1, Math.ceil(userCount * 0.1))
const bottomCount = topCount
const middle =
topCount + bottomCount < userCount ? ac.slice(topCount, -bottomCount) : ac
const middle = topCount + bottomCount < userCount ? ac.slice(topCount, -bottomCount) : ac
const totalAc = ac.reduce((sum, value) => sum + value, 0)
const totalSubmission = submissions.reduce((sum, value) => sum + value, 0)
const base: ClassComparison = {
@@ -250,17 +209,10 @@ classroomRoutes.post("/classes/comparison", async (c) => {
top10Avg: rounded(mean(ac.slice(0, topCount))),
middle80Avg: rounded(mean(middle)),
bottom10Avg: rounded(mean(ac.slice(-bottomCount))),
excellentRate: rounded(
(ac.filter((value) => value >= globalQ3).length / userCount) * 100,
),
passRate: rounded(
(ac.filter((value) => value >= globalQ1).length / userCount) * 100,
),
activeRate: rounded(
(submissions.filter((value) => value > 0).length / userCount) * 100,
),
acRate:
totalSubmission > 0 ? rounded((totalAc / totalSubmission) * 100) : 0,
excellentRate: rounded((ac.filter((value) => value >= globalQ3).length / userCount) * 100),
passRate: rounded((ac.filter((value) => value >= globalQ1).length / userCount) * 100),
activeRate: rounded((submissions.filter((value) => value > 0).length / userCount) * 100),
acRate: totalSubmission > 0 ? rounded((totalAc / totalSubmission) * 100) : 0,
compositeScore: 0,
}
if (hasTimeRange) {
@@ -290,9 +242,7 @@ classroomRoutes.post("/classes/comparison", async (c) => {
1,
)
}
comparisons.sort(
(a, b) => b.compositeScore - a.compositeScore || b.medianAc - a.medianAc,
)
comparisons.sort((a, b) => b.compositeScore - a.compositeScore || b.medianAc - a.medianAc)
return success(c, {
comparisons,
hasTimeRange,