统计接口会把时间窗内所有已完成提交的 criteria / feedback / suggestions 全取回来, 逐条走一遍 jieba。而前端的「全部时段」是不带 start 的 —— 攒一学年就得把所有评语 重新 cut 一遍,而这是个同步阻塞的请求。 只给词云的分词条数封顶(3000),并按时间倒序取,留下的是最近的那批。 **数值不封顶**:总数、均分、等级分布、各项平均分、完成人数仍然按整个时间窗精确 计算 —— 那只是已取回行上的算术,不额外花钱。这些数一旦采样,老师看到的完成率和 均分就是错的,而且从界面上完全看不出来;词云是辅助性的,看的是高频问题,取最近 这些条足够。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -158,6 +158,19 @@ flowchartRoutes.get("/flowcharts", requireAuth, async (c) => {
|
|||||||
|
|
||||||
const FLOWCHART_COMPLETED = 2
|
const FLOWCHART_COMPLETED = 2
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 词云的分词条数上限。
|
||||||
|
*
|
||||||
|
* 数值统计(总数、均分、等级分布、各项平均分、完成人数)仍然按整个时间窗**精确**
|
||||||
|
* 计算 —— 那只是已取回行上的算术,不额外花钱。真正会随数据量线性变重的是分词:
|
||||||
|
* 每条 feedback / suggestions / comment 都要走一遍 jieba,而前端的「全部时段」
|
||||||
|
* 是不带 start 的,攒一学年就得把所有评语重新 cut 一遍。
|
||||||
|
*
|
||||||
|
* 词云是辅助性的,看的是高频问题,取最近这些条足够;数值不能采样 —— 采了之后
|
||||||
|
* 老师看到的完成率和均分就是错的,而且从界面上看不出来。
|
||||||
|
*/
|
||||||
|
const WORDCLOUD_TEXT_LIMIT = 3000
|
||||||
|
|
||||||
flowchartRoutes.get("/flowcharts/statistics", requireTeacher, async (c) => {
|
flowchartRoutes.get("/flowcharts/statistics", requireTeacher, async (c) => {
|
||||||
const end = c.req.query("end")?.trim()
|
const end = c.req.query("end")?.trim()
|
||||||
if (!end) return failure(c, 400, "invalid-request", "end is required")
|
if (!end) return failure(c, 400, "invalid-request", "end is required")
|
||||||
@@ -211,6 +224,8 @@ flowchartRoutes.get("/flowcharts/statistics", requireTeacher, async (c) => {
|
|||||||
.from(schema.flowchartSubmission)
|
.from(schema.flowchartSubmission)
|
||||||
.innerJoin(schema.user, eq(schema.flowchartSubmission.userId, schema.user.id))
|
.innerJoin(schema.user, eq(schema.flowchartSubmission.userId, schema.user.id))
|
||||||
.where(and(...filters))
|
.where(and(...filters))
|
||||||
|
// 按时间倒序,好让词云取到的那部分是最近的
|
||||||
|
.orderBy(desc(schema.flowchartSubmission.createTime))
|
||||||
|
|
||||||
const empty = {
|
const empty = {
|
||||||
totalCount: 0,
|
totalCount: 0,
|
||||||
@@ -227,6 +242,9 @@ flowchartRoutes.get("/flowcharts/statistics", requireTeacher, async (c) => {
|
|||||||
const gradeDistribution: Record<string, number> = {}
|
const gradeDistribution: Record<string, number> = {}
|
||||||
const criteriaTotals = new Map<string, { sum: number; count: number; max: number }>()
|
const criteriaTotals = new Map<string, { sum: number; count: number; max: number }>()
|
||||||
const texts: string[] = []
|
const texts: string[] = []
|
||||||
|
const pushText = (value: string) => {
|
||||||
|
if (texts.length < WORDCLOUD_TEXT_LIMIT) texts.push(value)
|
||||||
|
}
|
||||||
const submitted = new Set<string>()
|
const submitted = new Set<string>()
|
||||||
let scoreSum = 0
|
let scoreSum = 0
|
||||||
let scoreCount = 0
|
let scoreCount = 0
|
||||||
@@ -255,10 +273,10 @@ flowchartRoutes.get("/flowcharts/statistics", requireTeacher, async (c) => {
|
|||||||
max: typeof detail.max === "number" ? detail.max : 100,
|
max: typeof detail.max === "number" ? detail.max : 100,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (typeof detail.comment === "string" && detail.comment) texts.push(detail.comment)
|
if (typeof detail.comment === "string" && detail.comment) pushText(detail.comment)
|
||||||
}
|
}
|
||||||
if (row.feedback) texts.push(row.feedback)
|
if (row.feedback) pushText(row.feedback)
|
||||||
if (row.suggestions) texts.push(row.suggestions)
|
if (row.suggestions) pushText(row.suggestions)
|
||||||
}
|
}
|
||||||
|
|
||||||
const criteriaAverages: Record<string, { avg: number; max: number }> = {}
|
const criteriaAverages: Record<string, { avg: number; max: number }> = {}
|
||||||
|
|||||||
Reference in New Issue
Block a user