refactor(modules): update classes, course-plans, diagnostic, questions, settings, student, layout

- Update classes data-access (invitations, main) for invitation management

- Update course-plans actions, data-access, and types

- Update diagnostic data-access for report queries

- Update questions data-access for question bank queries

- Update settings actions, ai-provider-settings-card, data-access, and types

- Update student course-filters, student-courses-view, student-schedule-filters, student-schedule-view

- Update layout app-sidebar, site-header, and navigation config
This commit is contained in:
SpecialX
2026-06-24 12:03:35 +08:00
parent c9e46f9f80
commit 8c2fe14c20
18 changed files with 712 additions and 189 deletions

View File

@@ -8,6 +8,7 @@ import { knowledgePointMastery, knowledgePoints } from "@/shared/db/schema"
import { getClassNameById, getActiveStudentIdsByClassId, getClassExists } from "@/modules/classes/data-access"
import { getExamSubmissionWithAnswers, getExamWithQuestionsForHomework } from "@/modules/exams/data-access"
import { getHomeworkSubmissionWithAnswersForMastery } from "@/modules/homework/data-access-error-collection"
import { getKnowledgePointsForQuestions } from "@/modules/questions/data-access"
import { getUserIdsByGradeId, getUserNamesByIds } from "@/modules/users/data-access"
@@ -141,6 +142,91 @@ export async function updateMasteryFromSubmission(submissionId: string): Promise
})
}
/**
* 从作业提交更新掌握度(累积模式)。
*
* 与 updateMasteryFromSubmission 类似,但数据来源是作业提交而非考试提交。
* 通过 homework 模块的跨模块接口获取作业提交的答案数据,避免直查 homeworkSubmissions 表。
*
* @param submissionId 作业提交 ID
*/
export async function updateMasteryFromHomeworkSubmission(submissionId: string): Promise<void> {
const submission = await getHomeworkSubmissionWithAnswersForMastery(submissionId)
if (!submission) return
const answers = submission.answers
if (answers.length === 0) return
const questionIds = Array.from(new Set(answers.map((a) => a.questionId)))
const kpMap = await getKnowledgePointsForQuestions(questionIds)
// Build a Map for O(1) answer lookup instead of find() in loop
const answerByQuestionId = new Map(answers.map((a) => [a.questionId, a]))
const kpStats = new Map<string, { total: number; correct: number }>()
for (const [questionId, kpLinks] of kpMap.entries()) {
const answer = answerByQuestionId.get(questionId)
if (!answer) continue
for (const link of kpLinks) {
const stat = kpStats.get(link.knowledgePointId) ?? { total: 0, correct: 0 }
stat.total += 1
if ((answer.score ?? 0) > 0) stat.correct += 1
kpStats.set(link.knowledgePointId, stat)
}
}
// 读取已有掌握度记录,累积计算(而非覆盖)
const existingRows = await db
.select()
.from(knowledgePointMastery)
.where(
and(
eq(knowledgePointMastery.studentId, submission.studentId),
inArray(knowledgePointMastery.knowledgePointId, Array.from(kpStats.keys())),
),
)
const existingByKp = new Map<string, { total: number; correct: number }>()
for (const row of existingRows) {
existingByKp.set(row.knowledgePointId, {
total: row.totalQuestions,
correct: row.correctQuestions,
})
}
const now = new Date()
// 使用事务保证多个 upsert 的原子性
await db.transaction(async (tx) => {
await Promise.all(
Array.from(kpStats.entries()).map(async ([kpId, stat]) => {
const existing = existingByKp.get(kpId)
const totalQuestions = (existing?.total ?? 0) + stat.total
const correctQuestions = (existing?.correct ?? 0) + stat.correct
const masteryLevel = computeMasteryLevel(correctQuestions, totalQuestions)
await tx
.insert(knowledgePointMastery)
.values({
studentId: submission.studentId,
knowledgePointId: kpId,
masteryLevel: String(masteryLevel),
totalQuestions,
correctQuestions,
lastAssessedAt: now,
})
.onDuplicateKeyUpdate({
set: {
masteryLevel: String(masteryLevel),
totalQuestions,
correctQuestions,
lastAssessedAt: now,
updatedAt: now,
},
})
}),
)
})
}
/**
* v3-P1-5从手动录入的成绩更新掌握度。
*