namespace YD_Event.Application.JudgeScore; /// /// 表演赛(个人花样基础套路赛)内置计分规则定义。 /// /// 只在初始化/同步规则库时使用;运行期的评分项一律从 ScoreRule / ScoreRuleItem 读取, /// 后台调整评分项、难度分值、席位配置后无需改代码。 /// /// /// 计分口径(裁判端原型 + 花样跳绳裁判端逻辑与测试验收说明书 6.7):
/// 席位:难度A组×1~3、难度B组×1~3、表现P×1~3、主裁T×1。
/// A 组裁判一人评 个人绳(DP+DM)、车轮跳(DA)、交互绳(DT_INTERACTION)、旅行跳(DT_TRAVEL) 四个类别,A=average(实际A组裁判);
/// B 组裁判一人评 个人绳(DR)、车轮跳(DB)、交互绳(DJ)、长绳(DL) 四个类别,B=average(实际B组裁判);
/// 总难度 D=(A+B)/2;表现 P=average(实际表现裁判)×0.05;
/// 规定元素 Q=max(0,1-0.05×缺失类别数),5 类为 个人绳、车轮跳、交互绳、长绳(6米以上)、旅行跳;
/// 扣分 M=max(0,1-(失误+违例)),表演赛不记间歇与空间违例;
/// 总分 R=max(0, D×(1+P)×Q×M)。 ///
///
public static class ShowcaseRuleDefinition { /// /// 表演赛规则编码 /// public const string RuleCode = "ROPE_SHOWCASE"; /// /// 表演赛规则名称 /// public const string RuleName = "表演赛计分规则"; /// /// A 组难度裁判角色类别编码。一人评个人绳、车轮跳、交互绳、旅行跳四个类别。 /// public const string RoleDifficultyGroupA = "DGA"; /// /// B 组难度裁判角色类别编码。一人评个人绳、车轮跳、交互绳、长绳四个类别。 /// public const string RoleDifficultyGroupB = "DGB"; /// /// 表现裁判角色类别编码 /// public const string RolePerformance = FreestyleRuleDefinition.RolePerformance; /// /// 主裁判角色类别编码 /// public const string RoleChief = FreestyleRuleDefinition.RoleChief; /// /// 规定元素目标类别数:个人绳、车轮跳、交互绳、长绳(6米以上)、旅行跳 /// public const decimal RequiredTarget = 5m; /// /// 长绳、旅行跳难度等级按脑图公式 L(x)=1.5^x 换算。 /// UnitValue 入库精度为 4 位小数,因此在此按四位小数固化,避免运行期浮点差异。 /// public static IReadOnlyList ExponentialDifficultyLevels { get; } = new List { new("DIFF_LEVEL_0", "0 级", 1m), new("DIFF_LEVEL_0_5", "0.5 级", 1.2247m), new("DIFF_LEVEL_1", "1 级", 1.5m), new("DIFF_LEVEL_2", "2 级", 2.25m), new("DIFF_LEVEL_3", "3 级", 3.375m), new("DIFF_LEVEL_4", "4 级", 5.0625m), new("DIFF_LEVEL_5", "5 级", 7.5938m), new("DIFF_LEVEL_6", "6 级", 11.3906m), new("DIFF_LEVEL_7", "7 级", 17.0859m), new("DIFF_LEVEL_8", "8 级", 25.6289m) }; /// /// A 组难度裁判要评的类别。 /// 逻辑说明书要求把脑图里两个同名 DT 拆成 DT_INTERACTION(交互绳)与 DT_TRAVEL(旅行跳),避免字段互相覆盖。 /// public static IReadOnlyList GroupACategories { get; } = new List { new("DP_DM", "个人绳", "DP+DM(个人绳)"), new("DA", "车轮跳", "DA(车轮跳)"), new("DT_INTERACTION", "交互绳", "DT(交互绳)"), new("DT_TRAVEL", "旅行跳", "DT(旅行跳)") }; /// /// B 组难度裁判要评的类别 /// public static IReadOnlyList GroupBCategories { get; } = new List { new("DR", "个人绳", "DR(个人绳)"), new("DB", "车轮跳", "DB(车轮跳)"), new("DJ", "交互绳", "DJ(交互绳)"), new("DL", "长绳", "DL(长绳)") }; /// /// 规定元素类别。成套动作中必须出现这 5 类,每缺一类参与 Q 扣减。 /// public static IReadOnlyList RequiredElements { get; } = new List { new("REQ_SINGLE_ROPE", "个人绳"), new("REQ_WHEEL", "车轮跳"), new("REQ_WIRE", "交互绳"), new("REQ_LONG_ROPE", "长绳"), new("REQ_TRAVEL", "旅行跳") }; /// /// 表演赛裁判席位配置:A/B 组难度裁判各 1~3 人、表现裁判 1~3 人、主裁 1 人 /// public const string JudgeRoleSchemaJson = """ [{"title":"主裁判","slots":[{"code":"T","name":"主裁判-T","min":1,"max":1}]},{"title":"难度裁判","slots":[{"code":"DGA","name":"难度裁判-A组裁判","min":1,"max":3,"categories":["DP+DM (个人绳)","DA (车轮跳)","DT (交互绳)","DT (旅行跳)"]},{"code":"DGB","name":"难度裁判-B组裁判","min":1,"max":3,"categories":["DR (个人绳)","DB (车轮跳)","DJ (交互绳)","DL (长绳)"]}]},{"title":"表现裁判","slots":[{"code":"P","name":"表现裁判-P","min":1,"max":3}]}] """; /// /// 表演赛计分参数配置。 /// totalMode=factor 表示按 R = D × (1 + P) × Q × M 相乘;A 组、B 组各自取平均后 difficultyDivisor=2 对应 D=(A+B)/2; /// 主裁同时承担 Q(规定元素缺失数)与 M(扣分合计)两个因子,因此配置了两条 T。 /// 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"]} """; /// /// 表演赛全部评分项 /// public static IReadOnlyList Items { get; } = BuildItems(); /// /// 构造表演赛评分项:A/B 组各 4 个类别的难度等级、表现五维与失误、主裁扣分与 5 类规定元素、取消资格原因 /// private static List BuildItems() { var items = new List(); var sort = 0; // 一名难度裁判要按类别分块评分,因此每个类别的难度等级项都用类别编码作前缀独立成项。 foreach (var category in GroupACategories) { items.AddRange(FreestyleRuleDefinition.BuildDifficultyItems(RoleDifficultyGroupA, category.Code, category.DisplayName, null, null, category.Code, ref sort, DifficultyLevelsFor(category))); } foreach (var category in GroupBCategories) { items.AddRange(FreestyleRuleDefinition.BuildDifficultyItems(RoleDifficultyGroupB, category.Code, category.DisplayName, null, null, category.Code, ref sort, DifficultyLevelsFor(category))); } 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; } private static IReadOnlyList DifficultyLevelsFor(ShowcaseDifficultyCategory category) => category.Code is "DT_TRAVEL" or "DL" ? ExponentialDifficultyLevels : FreestyleRuleDefinition.DifficultyLevels; } /// /// 表演赛一个难度类别的定义 /// /// 类别编码,同时作为评分项编码前缀与评分对象编码 /// 类别名称 /// 页面分块标题,与裁判端原型逐字一致 public record ShowcaseDifficultyCategory(string Code, string Name, string DisplayName);