实验成绩计算

This commit is contained in:
2026-08-09 22:40:21 +08:00 Unverified
parent d4abdd3db4
commit c25f16ec20
12 changed files with 7653 additions and 290 deletions
@@ -5,13 +5,85 @@ namespace Jiaowu.Api.Infrastructure.Grades;
public static class ExperimentGradeAggregationService
{
public static async Task RefreshTeachingTaskAsync(
AppDbContext db,
Guid teachingTaskId,
CancellationToken cancellationToken)
{
var sheets = await LoadPublishedSheetsAsync(
db,
teachingTaskId,
cancellationToken);
var existing = await db.ExperimentCourseGrades
.Where(x => x.TeachingTaskId == teachingTaskId)
.ToDictionaryAsync(x => x.StudentId, cancellationToken);
var studentIds = sheets
.SelectMany(x => x.Scores.Select(score => score.StudentId))
.Distinct()
.ToArray();
var refreshedAt = DateTime.UtcNow;
var totalWeight = sheets.Sum(x => x.ContributionWeight);
foreach (var studentId in studentIds)
{
var score = CalculateStudentScore(sheets, studentId);
if (!existing.Remove(studentId, out var aggregate))
{
aggregate = new Domain.Academic.ExperimentCourseGrade
{
TeachingTaskId = teachingTaskId,
StudentId = studentId
};
db.ExperimentCourseGrades.Add(aggregate);
}
aggregate.WeightedAverageScore = score;
aggregate.TotalWeight = totalWeight;
aggregate.PublishedProjectCount = sheets.Count;
aggregate.RefreshedAt = refreshedAt;
}
db.ExperimentCourseGrades.RemoveRange(existing.Values);
await db.SaveChangesAsync(cancellationToken);
}
public static async Task<ExperimentGradeAggregateResult> CalculateAsync(
AppDbContext db,
Guid teachingTaskId,
IReadOnlyCollection<Guid> studentIds,
CancellationToken cancellationToken)
{
var sheets = await db.ExperimentGradeSheets.AsNoTracking()
var sheets = await LoadPublishedSheetsAsync(
db,
teachingTaskId,
cancellationToken);
var requestedStudentIds = studentIds.Distinct().ToArray();
var persistedScores = await db.ExperimentCourseGrades.AsNoTracking()
.Where(x =>
x.TeachingTaskId == teachingTaskId &&
requestedStudentIds.Contains(x.StudentId))
.ToDictionaryAsync(
x => x.StudentId,
x => x.WeightedAverageScore,
cancellationToken);
var scores = requestedStudentIds.ToDictionary(
studentId => studentId,
studentId => persistedScores.GetValueOrDefault(studentId));
return new ExperimentGradeAggregateResult(
sheets.Count,
sheets.Select(x => new ExperimentGradeAggregateProject(
x.Id,
x.Code,
x.Name,
x.ContributionWeight)).ToList(),
scores);
}
private static Task<List<PublishedExperimentSheet>> LoadPublishedSheetsAsync(
AppDbContext db,
Guid teachingTaskId,
CancellationToken cancellationToken) =>
db.ExperimentGradeSheets.AsNoTracking()
.Where(x =>
x.Status ==
Domain.Academic.ExperimentGradeSheetStatus.Published &&
@@ -28,40 +100,27 @@ public static class ExperimentGradeAggregationService
.AsSplitQuery()
.ToListAsync(cancellationToken);
var scores = new Dictionary<Guid, decimal?>();
foreach (var studentId in studentIds.Distinct())
private static decimal? CalculateStudentScore(
IReadOnlyCollection<PublishedExperimentSheet> sheets,
Guid studentId)
{
decimal weightedTotal = 0;
decimal totalWeight = 0;
if (sheets.Count == 0) return null;
foreach (var sheet in sheets)
{
decimal weightedTotal = 0;
decimal totalWeight = 0;
var complete = sheets.Count > 0;
foreach (var sheet in sheets)
{
var score = sheet.Scores.FirstOrDefault(x =>
x.StudentId == studentId);
if (score?.TotalScore is not decimal totalScore)
{
complete = false;
break;
}
weightedTotal += totalScore * sheet.ContributionWeight;
totalWeight += sheet.ContributionWeight;
}
scores[studentId] = complete && totalWeight > 0
? Math.Round(
weightedTotal / totalWeight,
1,
MidpointRounding.AwayFromZero)
: null;
var score = sheet.Scores.FirstOrDefault(x =>
x.StudentId == studentId);
if (score?.TotalScore is not decimal totalScore) return null;
weightedTotal += totalScore * sheet.ContributionWeight;
totalWeight += sheet.ContributionWeight;
}
return new ExperimentGradeAggregateResult(
sheets.Count,
sheets.Select(x => new ExperimentGradeAggregateProject(
x.Id,
x.Code,
x.Name,
x.ContributionWeight)).ToList(),
scores);
return totalWeight > 0
? Math.Round(
weightedTotal / totalWeight,
1,
MidpointRounding.AwayFromZero)
: null;
}
private sealed record PublishedExperimentSheet(