实验成绩计算
This commit is contained in:
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user