YD_Event.Admin/YD_Event.Application/JudgeScore/ShowcaseRuleDefinition.cs
2026-08-23 16:21:49 +08:00

172 lines
8.9 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

namespace YD_Event.Application.JudgeScore;
/// <summary>
/// 表演赛(个人花样基础套路赛)内置计分规则定义。
/// <para>
/// 只在初始化/同步规则库时使用;运行期的评分项一律从 ScoreRule / ScoreRuleItem 读取,
/// 后台调整评分项、难度分值、席位配置后无需改代码。
/// </para>
/// <para>
/// 计分口径(裁判端原型 + 花样跳绳裁判端逻辑与测试验收说明书 6.7):<br/>
/// 席位:难度A组×3、难度B组×3、表现P×3、主裁T×1。<br/>
/// A 组裁判一人评 个人绳(DP+DM)、车轮跳(DA)、交互绳(DT_INTERACTION)、旅行跳(DT_TRAVEL) 四个类别,A=average(A1,A2,A3);<br/>
/// B 组裁判一人评 个人绳(DR)、车轮跳(DB)、交互绳(DJ)、长绳(DL) 四个类别,B=average(B1,B2,B3);<br/>
/// 总难度 D=(A+B)/2;表现 P=average(p1,p2,p3)×0.05;<br/>
/// 规定元素 Q=max(0,1-0.05×缺失类别数),5 类为 个人绳、车轮跳、交互绳、长绳(6米以上)、旅行跳;<br/>
/// 扣分 M=max(0,1-(失误+违例)),表演赛不记间歇与空间违例;<br/>
/// 总分 R=max(0, D×(1+P)×Q×M)。
/// </para>
/// </summary>
public static class ShowcaseRuleDefinition
{
/// <summary>
/// 表演赛规则编码
/// </summary>
public const string RuleCode = "ROPE_SHOWCASE";
/// <summary>
/// 表演赛规则名称
/// </summary>
public const string RuleName = "表演赛计分规则";
/// <summary>
/// A 组难度裁判角色类别编码。一人评个人绳、车轮跳、交互绳、旅行跳四个类别。
/// </summary>
public const string RoleDifficultyGroupA = "DGA";
/// <summary>
/// B 组难度裁判角色类别编码。一人评个人绳、车轮跳、交互绳、长绳四个类别。
/// </summary>
public const string RoleDifficultyGroupB = "DGB";
/// <summary>
/// 表现裁判角色类别编码
/// </summary>
public const string RolePerformance = FreestyleRuleDefinition.RolePerformance;
/// <summary>
/// 主裁判角色类别编码
/// </summary>
public const string RoleChief = FreestyleRuleDefinition.RoleChief;
/// <summary>
/// 规定元素目标类别数:个人绳、车轮跳、交互绳、长绳(6米以上)、旅行跳
/// </summary>
public const decimal RequiredTarget = 5m;
/// <summary>
/// A 组难度裁判要评的类别。
/// 逻辑说明书要求把脑图里两个同名 DT 拆成 DT_INTERACTION(交互绳)与 DT_TRAVEL(旅行跳),避免字段互相覆盖。
/// </summary>
public static IReadOnlyList<ShowcaseDifficultyCategory> GroupACategories { get; } = new List<ShowcaseDifficultyCategory>
{
new("DP_DM", "个人绳", "DP+DM(个人绳)"),
new("DA", "车轮跳", "DA(车轮跳)"),
new("DT_INTERACTION", "交互绳", "DT(交互绳)"),
new("DT_TRAVEL", "旅行跳", "DT(旅行跳)")
};
/// <summary>
/// B 组难度裁判要评的类别
/// </summary>
public static IReadOnlyList<ShowcaseDifficultyCategory> GroupBCategories { get; } = new List<ShowcaseDifficultyCategory>
{
new("DR", "个人绳", "DR(个人绳)"),
new("DB", "车轮跳", "DB(车轮跳)"),
new("DJ", "交互绳", "DJ(交互绳)"),
new("DL", "长绳", "DL(长绳)")
};
/// <summary>
/// 规定元素类别。成套动作中必须出现这 5 类,每缺一类参与 Q 扣减。
/// </summary>
public static IReadOnlyList<RequiredElementDefinition> RequiredElements { get; } = new List<RequiredElementDefinition>
{
new("REQ_SINGLE_ROPE", "个人绳"),
new("REQ_WHEEL", "车轮跳"),
new("REQ_WIRE", "交互绳"),
new("REQ_LONG_ROPE", "长绳"),
new("REQ_TRAVEL", "旅行跳")
};
/// <summary>
/// 表演赛裁判席位配置:A/B 组难度裁判各 3 人、表现裁判 3 人、主裁 1 人
/// </summary>
public const string JudgeRoleSchemaJson = """
[{"title":"难度裁判","slots":[{"code":"DGA","name":"难度裁判-A组裁判","min":3},{"code":"DGB","name":"难度裁判-B组裁判","min":3}]},{"title":"表现裁判","slots":[{"code":"P","name":"表现裁判-P","min":3}]},{"title":"主裁判","slots":[{"code":"T","name":"主裁判-T","min":1,"max":1}]}]
""";
/// <summary>
/// 表演赛计分参数配置。
/// totalMode=factor 表示按 R = D × (1 + P) × Q × M 相乘;A 组、B 组各自取平均后 difficultyDivisor=2 对应 D=(A+B)/2;
/// 主裁同时承担 Q(规定元素缺失数)与 M(扣分合计)两个因子,因此配置了两条 T。
/// </summary>
public const string RuleConfigJson = """
{"ruleType":"SHOWCASE","totalMode":"factor","baseScore":0,"scoreDecimals":2,"difficultyDivisor":2,"correctionNeedApproval":false,"roleAggregations":[{"roleType":"DGA","symbol":"A","label":"A组难度","sourceField":"DifficultyScore","targetField":"DifficultyScore","aggregate":"avg","sign":1,"factor":"D","factorWeight":1},{"roleType":"DGB","symbol":"B","label":"B组难度","sourceField":"DifficultyScore","targetField":"DifficultyScore","aggregate":"avg","sign":1,"factor":"D","factorWeight":1},{"roleType":"P","symbol":"P","label":"表现","sourceField":"PerformanceScore","targetField":"PerformanceScore","aggregate":"avg","sign":1,"factor":"P","factorWeight":0.05},{"roleType":"T","symbol":"Q","label":"规定元素缺失","sourceField":"RequiredMissingCount","targetField":"RequiredScore","aggregate":"max","sign":1,"factor":"Q","factorWeight":0.05},{"roleType":"T","symbol":"M","label":"扣分","sourceField":"DeductionScore","targetField":"DeductionScore","aggregate":"sum","sign":-1,"factor":"M","factorWeight":1}],"tieBreakers":["TotalScore","-DeductionScore","RequiredScore","PerformanceScore","DifficultyScore"]}
""";
/// <summary>
/// 表演赛全部评分项
/// </summary>
public static IReadOnlyList<PrescribedRuleItemDefinition> Items { get; } = BuildItems();
/// <summary>
/// 构造表演赛评分项:A/B 组各 4 个类别的难度等级、表现五维与失误、主裁扣分与 5 类规定元素、取消资格原因
/// </summary>
private static List<PrescribedRuleItemDefinition> BuildItems()
{
var items = new List<PrescribedRuleItemDefinition>();
var sort = 0;
// 一名难度裁判要按类别分块评分,因此每个类别的难度等级项都用类别编码作前缀独立成项。
foreach (var category in GroupACategories)
{
items.AddRange(FreestyleRuleDefinition.BuildDifficultyItems(RoleDifficultyGroupA,
category.Code, category.DisplayName, null, null, category.Code, ref sort));
}
foreach (var category in GroupBCategories)
{
items.AddRange(FreestyleRuleDefinition.BuildDifficultyItems(RoleDifficultyGroupB,
category.Code, category.DisplayName, null, null, category.Code, ref sort));
}
items.AddRange(FreestyleRuleDefinition.BuildPerformanceItems(ref sort));
// 表演赛主裁只记失误与违例,不记间歇、空间违例。
items.AddRange(FreestyleRuleDefinition.BuildChiefDeductionItems(null, null, ref sort));
// 规定元素:5 类中每类只要有动作即计一类完成,缺失类别数由服务端算出后参与 Q。
foreach (var element in RequiredElements)
{
items.Add(new PrescribedRuleItemDefinition
{
JudgeRoleType = RoleChief, ItemCode = element.ItemCode, ItemName = element.ItemName,
ItemType = ScoreItemType.RequiredElement, ScoreDimension = ScoreDimensionType.Required,
CalcMode = ScoreCalcMode.CountByUnit,
UnitValue = 1m, MinValue = 0m, MaxValue = 50m, StepValue = 1m, DefaultValue = 0m,
IsRequired = false, Sort = ++sort,
ExtensionJson = FreestyleItemExtension.RequiredElement()
});
}
items.Add(new PrescribedRuleItemDefinition
{
JudgeRoleType = RoleChief, ItemCode = "REQUIRED_MISSING", ItemName = "规定元素缺失(个)",
ItemType = ScoreItemType.RequiredElement, ScoreDimension = ScoreDimensionType.None,
CalcMode = ScoreCalcMode.DirectValue,
MinValue = 0m, MaxValue = RequiredTarget, StepValue = 1m, DefaultValue = 0m,
IsRequired = false, Sort = ++sort,
ExtensionJson = FreestyleItemExtension.RequiredMissing(RequiredTarget)
});
items.AddRange(FreestyleRuleDefinition.BuildDisqualifyItems(ref sort));
return items;
}
}
/// <summary>
/// 表演赛一个难度类别的定义
/// </summary>
/// <param name="Code">类别编码,同时作为评分项编码前缀与评分对象编码</param>
/// <param name="Name">类别名称</param>
/// <param name="DisplayName">页面分块标题,与裁判端原型逐字一致</param>
public record ShowcaseDifficultyCategory(string Code, string Name, string DisplayName);