feat(班级PK): 班级 PK 只比两个班都布置过的同一批题,按做对的人占几成分输赢;登录了就能看,AI 分析由服务端现算
- 新接口 GET /rankings/pk:一个班过半的人交过算布置过,至少两个班布置过的题才比; 显示的整数百分比一样算平;口径和排名页一样(这学期、比赛不算、不计入排名的人不算) - 不给班就用自己的班(老师用默认的班),只给一个班就配同年级一起布置过的题最多的班, 一样多挑班级对抗排得更前的 - 去掉 POST /classes/comparison(原来不登录也能查,综合分、四分位数、优秀率都按所选几个班算) - /ai/class-pk-analysis 只收班号和时间段,数字和页面上的是同一份 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
3c6e4a1e7c
commit
d83804b814
7 files changed
+390
-240
No files matched your search
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
classPkPeriodSchema,
|
||||
rankHiddenRequestSchema,
|
||||
rankPeriodSchema,
|
||||
rankScopeSchema,
|
||||
@@ -13,7 +14,7 @@ import {
|
||||
import { and, countDistinct, desc, eq, gte, inArray, isNotNull, ne } from "drizzle-orm"
|
||||
import { Hono } from "hono"
|
||||
|
||||
import { optionalAuth, requireTeacher, type AppEnv } from "../auth/middleware"
|
||||
import { optionalAuth, requireAuth, requireTeacher, type AppEnv } from "../auth/middleware"
|
||||
import { db, schema } from "../db"
|
||||
import { failure, parseBody, success } from "../http"
|
||||
import { dayStart, termStart, weekStart } from "../time"
|
||||
@@ -21,7 +22,10 @@ import {
|
||||
audienceWhere,
|
||||
classBattle,
|
||||
classDetail,
|
||||
classPk,
|
||||
loadEntrants,
|
||||
PK_MAX_CLASSES,
|
||||
pkPartner,
|
||||
rankedStudents,
|
||||
standings,
|
||||
WEEK_MS,
|
||||
@@ -252,6 +256,38 @@ rankingRoutes.get("/rankings/class-detail", optionalAuth, async (c) => {
|
||||
return success(c, await classDetail(className, isTeacherOrAbove(c.get("user"))))
|
||||
})
|
||||
|
||||
/**
|
||||
* 班级 PK(设计稿「班级 PK 重设计」定稿):`classes` 逗号分隔。谁登录了都能看 —— 只有班级
|
||||
* 层面的数,没有点名到人。只给一个班(或者不给,用自己的班 / 老师默认的班)就配一个对手,
|
||||
* 见 `pkPartner`。
|
||||
*/
|
||||
rankingRoutes.get("/rankings/pk", requireAuth, async (c) => {
|
||||
const user = c.get("user")!
|
||||
const period = classPkPeriodSchema.safeParse(c.req.query("period") ?? "term")
|
||||
if (!period.success) return failure(c, 400, "invalid-query", "时间段不对")
|
||||
const requested = [
|
||||
...new Set(
|
||||
(c.req.query("classes") ?? "")
|
||||
.split(",")
|
||||
.map((name) => name.trim())
|
||||
.filter((name) => name && name.length <= 32),
|
||||
),
|
||||
]
|
||||
if (requested.length > PK_MAX_CLASSES)
|
||||
return failure(c, 400, "too-many-classes", `一次最多比 ${PK_MAX_CLASSES} 个班`)
|
||||
|
||||
const isTeacher = isTeacherOrAbove(user)
|
||||
const mine = isTeacher ? null : user.className || null
|
||||
const classNames = requested.length
|
||||
? requested
|
||||
: [isTeacher ? await defaultClassFor(user.id) : mine].filter((name): name is string => !!name)
|
||||
if (classNames.length === 1) {
|
||||
const partner = await pkPartner(classNames[0]!)
|
||||
if (partner) classNames.push(partner)
|
||||
}
|
||||
return success(c, await classPk(classNames, period.data, mine))
|
||||
})
|
||||
|
||||
/** 本班每周冠军:最近几个已经结束的周,每周新做对最多的那个人(一样多先做到的) */
|
||||
rankingRoutes.get("/rankings/champions", optionalAuth, async (c) => {
|
||||
const className = await resolveGroup(c.get("user"), "class", c.req.query("className")?.trim())
|
||||
|
||||
Reference in new issue
Block a user