新增 result_statistics、registration_result_summaries 两张统计表,现有数据库首次启动时自动回填。 SQL 查询首先通过 examId 限定范围。 科目成绩只排序一次,并使用 registration/subject 字典组织数据。 汇总接口与成绩明细接口已拆分,明细、成绩录入名单和特征分名单均改为服务端分页。 所有成绩写入口统一限制为:0—满分,且只能是整数或 .5,包括手工录入、批量录入、Excel 导入和复议改分。 前端也增加了相同的即时校验。 修复其他考试卡片显示 undefined 的问题。
121 lines
4.6 KiB
C#
121 lines
4.6 KiB
C#
using System.Text.Json.Nodes;
|
|
using Eis.Infrastructure.Administration;
|
|
|
|
namespace Eis.Infrastructure.Tests.Administration;
|
|
|
|
public sealed class AdminResultStatisticsTests
|
|
{
|
|
[Theory]
|
|
[InlineData(0, 100, true)]
|
|
[InlineData(10, 100, true)]
|
|
[InlineData(10.5, 100, true)]
|
|
[InlineData(10.1, 100, false)]
|
|
[InlineData(-0.5, 100, false)]
|
|
[InlineData(100.5, 100, false)]
|
|
public void IsValidScore_OnlyAcceptsWholeOrHalfPoints(double score, double fullScore, bool expected)
|
|
{
|
|
Assert.Equal(expected, AdminResultStatistics.IsValidScore(score, fullScore));
|
|
}
|
|
|
|
[Fact]
|
|
public void Calculate_ComputesEachSubjectAndTotalRankingOnceForTheExam()
|
|
{
|
|
var exam = Exam("rank_percent", 50);
|
|
var registrations = new[] { Registration("r1"), Registration("r2"), Registration("r3") };
|
|
var results = new[]
|
|
{
|
|
Result("r1-s1", "r1", "s1", 90), Result("r1-s2", "r1", "s2", 80),
|
|
Result("r2-s1", "r2", "s1", 90), Result("r2-s2", "r2", "s2", 70),
|
|
Result("r3-s1", "r3", "s1", 60), Result("r3-s2", "r3", "s2", 70)
|
|
};
|
|
|
|
var calculation = AdminResultStatistics.Calculate(exam, registrations, results, "2026-07-23T00:00:00.000Z");
|
|
|
|
Assert.Equal(6, calculation.Metrics.Count);
|
|
Assert.Equal(1, calculation.Metrics.Single(item => item.ResultId == "r1-s1").Rank);
|
|
Assert.Equal(1, calculation.Metrics.Single(item => item.ResultId == "r2-s1").Rank);
|
|
Assert.Equal(3, calculation.Metrics.Single(item => item.ResultId == "r3-s1").Rank);
|
|
Assert.Equal(2, calculation.Metrics.Single(item => item.ResultId == "r2-s2").Rank);
|
|
Assert.Equal(2, calculation.Metrics.Single(item => item.ResultId == "r3-s2").Rank);
|
|
|
|
var summaries = calculation.Summaries.ToDictionary(item => item.RegistrationId);
|
|
Assert.Equal(170, summaries["r1"].TotalScore);
|
|
Assert.Equal(1, summaries["r1"].Rank);
|
|
Assert.True(summaries["r1"].Qualified);
|
|
Assert.Equal(2, summaries["r2"].Rank);
|
|
Assert.True(summaries["r2"].Qualified);
|
|
Assert.Equal(3, summaries["r3"].Rank);
|
|
Assert.False(summaries["r3"].Qualified);
|
|
Assert.All(summaries.Values, item => Assert.Equal(3, item.CohortSize));
|
|
}
|
|
|
|
[Fact]
|
|
public void Calculate_PersistsIncompleteAndDraftStatesWithoutIncludingDraftInPublishedSummary()
|
|
{
|
|
var exam = Exam("subject_scores", 0);
|
|
var registrations = new[] { Registration("r1"), Registration("r2") };
|
|
var results = new[]
|
|
{
|
|
Result("r1-s1", "r1", "s1", 95),
|
|
Result("r1-s2", "r1", "s2", 88, published: false),
|
|
Result("r2-s1", "r2", "s1", 80),
|
|
Result("r2-s2", "r2", "s2", 75)
|
|
};
|
|
|
|
var calculation = AdminResultStatistics.Calculate(exam, registrations, results, "2026-07-23T00:00:00.000Z");
|
|
var draft = calculation.Metrics.Single(item => item.ResultId == "r1-s2");
|
|
var summaries = calculation.Summaries.ToDictionary(item => item.RegistrationId);
|
|
|
|
Assert.Equal("待发布", draft.Grade);
|
|
Assert.Equal(2, draft.CohortSize);
|
|
Assert.False(summaries["r1"].Complete);
|
|
Assert.Null(summaries["r1"].Qualified);
|
|
Assert.Equal(1, summaries["r1"].PublishedSubjectCount);
|
|
Assert.True(summaries["r2"].Complete);
|
|
}
|
|
|
|
private static OperationalExam Exam(string passPolicy, double passValue)
|
|
{
|
|
var subjects = new[]
|
|
{
|
|
new JsonObject { ["id"] = "s1", ["name"] = "语文", ["fullScore"] = 100, ["passRule"] = "fixed_score", ["passValue"] = 60 },
|
|
new JsonObject { ["id"] = "s2", ["name"] = "数学", ["fullScore"] = 100, ["passRule"] = "fixed_score", ["passValue"] = 60 }
|
|
};
|
|
return new("exam-1", new JsonObject
|
|
{
|
|
["id"] = "exam-1",
|
|
["passPolicy"] = passPolicy,
|
|
["passValue"] = passValue
|
|
}, subjects);
|
|
}
|
|
|
|
private static OperationalRegistration Registration(string id) => new(
|
|
id,
|
|
$"user-{id}",
|
|
"exam-1",
|
|
"approved",
|
|
"paid",
|
|
null,
|
|
"2026-07-23T00:00:00.000Z",
|
|
new JsonObject
|
|
{
|
|
["id"] = id,
|
|
["subjectIds"] = new JsonArray("s1", "s2")
|
|
});
|
|
|
|
private static AdminWorkflowResult Result(
|
|
string id,
|
|
string registrationId,
|
|
string subjectId,
|
|
double score,
|
|
bool published = true) => new(
|
|
id,
|
|
registrationId,
|
|
subjectId,
|
|
score,
|
|
published ? "" : "待发布",
|
|
published,
|
|
"2026-07-23T00:00:00.000Z",
|
|
published ? "2026-07-23T00:00:00.000Z" : null);
|
|
}
|