namespace YD_Event.Application.JudgeScore;
///
/// 表演赛(个人花样基础套路赛)内置计分规则定义。
///
/// 只在初始化/同步规则库时使用;运行期的评分项一律从 ScoreRule / ScoreRuleItem 读取,
/// 后台调整评分项、难度分值、席位配置后无需改代码。
///
///
/// 计分口径(裁判端原型 + 花样跳绳裁判端逻辑与测试验收说明书 6.7):
/// 席位:难度A组×3、难度B组×3、表现P×3、主裁T×1。
/// A 组裁判一人评 个人绳(DP+DM)、车轮跳(DA)、交互绳(DT_INTERACTION)、旅行跳(DT_TRAVEL) 四个类别,A=average(A1,A2,A3);
/// B 组裁判一人评 个人绳(DR)、车轮跳(DB)、交互绳(DJ)、长绳(DL) 四个类别,B=average(B1,B2,B3);
/// 总难度 D=(A+B)/2;表现 P=average(p1,p2,p3)×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;
///
/// 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));
}
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;
}
}
///
/// 表演赛一个难度类别的定义
///
/// 类别编码,同时作为评分项编码前缀与评分对象编码
/// 类别名称
/// 页面分块标题,与裁判端原型逐字一致
public record ShowcaseDifficultyCategory(string Code, string Name, string DisplayName);