学业预警检测的其余三类规则也已批量化:
低绩点:按学生分组计算 GPA。 缺勤/迟到:按学生分组统计。 延毕风险:按学生分组统计累计不及格。 三类规则都一次性加载已有预警集合后去重,不再逐学生查库。
This commit is contained in:
@@ -232,51 +232,43 @@ public sealed class WarningsController(AppDbContext db, ICurrentUserDataScope sc
|
||||
|
||||
private async Task<List<WarningRecord>> DetectLowGPA(WarningRule rule, Guid termId, List<StudentInfo> students, CancellationToken ct)
|
||||
{
|
||||
var records = new List<WarningRecord>();
|
||||
foreach (var s in students)
|
||||
{
|
||||
var grades = await db.GradeRecords.Where(x => x.StudentId == s.Id && x.GradeSheet!.TeachingTask!.AcademicTermId == termId && x.GradeSheet.Status == GradeSheetStatus.Published && x.GradePoint != null).ToListAsync(ct);
|
||||
if (grades.Count == 0) continue;
|
||||
var gpa = grades.Average(x => x.GradePoint!.Value);
|
||||
if (gpa < rule.Threshold)
|
||||
{
|
||||
if (await db.WarningRecords.AnyAsync(x => x.StudentId == s.Id && x.AcademicTermId == termId && x.Type == WarningType.LowGPA, ct)) continue;
|
||||
records.Add(new WarningRecord { StudentId = s.Id, Type = rule.Type, TriggerValue = Math.Round(gpa, 2), Detail = $"平均绩点 {gpa:F2},低于预警阈值 {rule.Threshold}。", AcademicTermId = termId });
|
||||
}
|
||||
}
|
||||
return records;
|
||||
var studentIds = students.Select(student => student.Id).ToArray();
|
||||
var existingStudentIds = await ExistingWarningStudentIdsAsync(rule.Type, termId, ct);
|
||||
var gpaByStudent = await db.GradeRecords.AsNoTracking()
|
||||
.Where(record => studentIds.Contains(record.StudentId) &&
|
||||
record.GradeSheet!.TeachingTask!.AcademicTermId == termId &&
|
||||
record.GradeSheet.Status == GradeSheetStatus.Published &&
|
||||
record.GradePoint != null)
|
||||
.GroupBy(record => record.StudentId)
|
||||
.Select(group => new { StudentId = group.Key, Gpa = group.Average(record => record.GradePoint!.Value) })
|
||||
.ToListAsync(ct);
|
||||
return gpaByStudent.Where(item => item.Gpa < rule.Threshold && !existingStudentIds.Contains(item.StudentId))
|
||||
.Select(item => new WarningRecord { StudentId = item.StudentId, Type = rule.Type, TriggerValue = Math.Round(item.Gpa, 2), Detail = $"平均绩点 {item.Gpa:F2},低于预警阈值 {rule.Threshold}。", AcademicTermId = termId }).ToList();
|
||||
}
|
||||
|
||||
private async Task<List<WarningRecord>> DetectAbsenteeism(WarningRule rule, Guid termId, List<StudentInfo> students, CancellationToken ct)
|
||||
{
|
||||
var records = new List<WarningRecord>();
|
||||
foreach (var s in students)
|
||||
{
|
||||
var absent = await db.AttendanceRecords.CountAsync(x => x.StudentId == s.Id && x.AttendanceSheet!.Status == AttendanceSheetStatus.Submitted && x.AttendanceSheet.TeachingTask!.AcademicTermId == termId && (x.Status == AttendanceStatus.Absent || x.Status == AttendanceStatus.Late), ct);
|
||||
if (absent >= rule.Threshold)
|
||||
{
|
||||
if (await db.WarningRecords.AnyAsync(x => x.StudentId == s.Id && x.AcademicTermId == termId && x.Type == WarningType.Absenteeism, ct)) continue;
|
||||
records.Add(new WarningRecord { StudentId = s.Id, Type = rule.Type, TriggerValue = absent, Detail = $"缺勤/迟到 {absent} 次,达到预警阈值 {rule.Threshold} 次。", AcademicTermId = termId });
|
||||
}
|
||||
}
|
||||
return records;
|
||||
var studentIds = students.Select(student => student.Id).ToArray();
|
||||
var existingStudentIds = await ExistingWarningStudentIdsAsync(rule.Type, termId, ct);
|
||||
var absencesByStudent = await db.AttendanceRecords.AsNoTracking()
|
||||
.Where(record => studentIds.Contains(record.StudentId) && record.AttendanceSheet!.Status == AttendanceSheetStatus.Submitted && record.AttendanceSheet.TeachingTask!.AcademicTermId == termId && (record.Status == AttendanceStatus.Absent || record.Status == AttendanceStatus.Late))
|
||||
.GroupBy(record => record.StudentId).Select(group => new { StudentId = group.Key, Absent = group.Count() }).ToListAsync(ct);
|
||||
return absencesByStudent.Where(item => item.Absent >= rule.Threshold && !existingStudentIds.Contains(item.StudentId))
|
||||
.Select(item => new WarningRecord { StudentId = item.StudentId, Type = rule.Type, TriggerValue = item.Absent, Detail = $"缺勤/迟到 {item.Absent} 次,达到预警阈值 {rule.Threshold} 次。", AcademicTermId = termId }).ToList();
|
||||
}
|
||||
|
||||
private async Task<List<WarningRecord>> DetectGraduationDelay(WarningRule rule, Guid termId, List<StudentInfo> students, CancellationToken ct)
|
||||
{
|
||||
var records = new List<WarningRecord>();
|
||||
foreach (var s in students)
|
||||
{
|
||||
var total = await db.GradeRecords.CountAsync(x => x.StudentId == s.Id && x.GradeSheet!.Status == GradeSheetStatus.Published && x.TotalScore < 60, ct);
|
||||
if (total >= rule.Threshold)
|
||||
{
|
||||
if (await db.WarningRecords.AnyAsync(x => x.StudentId == s.Id && x.AcademicTermId == termId && x.Type == WarningType.GraduationDelay, ct)) continue;
|
||||
records.Add(new WarningRecord { StudentId = s.Id, Type = rule.Type, TriggerValue = total, Detail = $"累计不及格 {total} 门,达到延毕预警阈值 {rule.Threshold} 门。", AcademicTermId = termId });
|
||||
}
|
||||
}
|
||||
return records;
|
||||
var studentIds = students.Select(student => student.Id).ToArray();
|
||||
var existingStudentIds = await ExistingWarningStudentIdsAsync(rule.Type, termId, ct);
|
||||
var failedByStudent = await db.GradeRecords.AsNoTracking().Where(record => studentIds.Contains(record.StudentId) && record.GradeSheet!.Status == GradeSheetStatus.Published && record.TotalScore < 60).GroupBy(record => record.StudentId).Select(group => new { StudentId = group.Key, Failed = group.Count() }).ToListAsync(ct);
|
||||
return failedByStudent.Where(item => item.Failed >= rule.Threshold && !existingStudentIds.Contains(item.StudentId))
|
||||
.Select(item => new WarningRecord { StudentId = item.StudentId, Type = rule.Type, TriggerValue = item.Failed, Detail = $"累计不及格 {item.Failed} 门,达到延毕预警阈值 {rule.Threshold} 门。", AcademicTermId = termId }).ToList();
|
||||
}
|
||||
|
||||
private Task<HashSet<Guid>> ExistingWarningStudentIdsAsync(WarningType type, Guid termId, CancellationToken ct) =>
|
||||
db.WarningRecords.AsNoTracking().Where(record => record.AcademicTermId == termId && record.Type == type).Select(record => record.StudentId).ToHashSetAsync(ct);
|
||||
|
||||
private async Task<Guid?> GetStudentIdAsync(CancellationToken ct) => await db.Students.Where(s => s.UserId == scope.Current.UserId).Select(s => (Guid?)s.Id).FirstOrDefaultAsync(ct);
|
||||
private ActionResult StudentNotFound() => Conflict(new ProblemDetails { Title = "未关联学生档案", Status = 409 });
|
||||
private ActionResult ConflictProblem(string d) => Conflict(new ProblemDetails { Title = "操作失败", Detail = d, Status = 409 });
|
||||
|
||||
Reference in New Issue
Block a user