YD_Event.Admin/YD_Event.Application/JudgeScore/FreestyleRuleDefinition.cs
ltx 44065282e3 工作台裁判列按裁判类型展示对应分值,不再统一错误地读取评分单总分。
主裁列现在展示最终汇总扣分,已包含表现裁判和主裁的失误次数平均换算结果。
2026-09-28 14:26:18 +08:00

544 lines
27 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>
/// 计分口径(裁判端原型 + 花样跳绳裁判端逻辑与测试验收说明书):<br/>
/// 车轮跳花样:DA×2、DB×2、P×2、T×1;dA=(da1+da2)/2、dB=(db1+db2)/2、D=(dA+dB)/2;<br/>
/// P=average(p1,p2)×0.05;Q=max(0,1-0.05×规定元素缺失类别数);M=max(0,1-(失误+违例+间歇));<br/>
/// 总分 R=max(0, D×(1+P)×Q×M)。<br/>
/// 交互绳花样:DJ×2、DT×2、P×2、T×1;DJ 按全部动作取均值、DT 取最高 20 个动作(分母固定 20);<br/>
/// dJ=(DJ1+DJ2)/2、dT=(DT1+DT2)/2、D=dJ×1.4+dT;P=average(p1,p2)×0.033;<br/>
/// M=max(0,1-(失误+违例+空间违例));总分 R=max(0, D×(1+P)×M),不计 Q。
/// </para>
/// </summary>
public static class FreestyleRuleDefinition
{
/// <summary>
/// 车轮跳花样规则编码
/// </summary>
public const string WheelRuleCode = "ROPE_FREESTYLE_WHEEL";
/// <summary>
/// 车轮跳花样规则名称
/// </summary>
public const string WheelRuleName = "车轮跳花样计分规则";
/// <summary>
/// 交互绳花样规则编码
/// </summary>
public const string WireRuleCode = "ROPE_FREESTYLE_WIRE";
/// <summary>
/// 交互绳花样规则名称
/// </summary>
public const string WireRuleName = "交互绳花样计分规则";
/// <summary>
/// 运动员A难度裁判角色类别编码(车轮跳花样)
/// </summary>
public const string RoleDifficultyA = "DA";
/// <summary>
/// 运动员B难度裁判角色类别编码(车轮跳花样)
/// </summary>
public const string RoleDifficultyB = "DB";
/// <summary>
/// 跳绳运动员难度裁判角色类别编码(交互绳花样,按全部动作取均值)
/// </summary>
public const string RoleDifficultyJumper = "DJ";
/// <summary>
/// 摇绳运动员难度裁判角色类别编码(交互绳花样,取最高 20 个动作)
/// </summary>
public const string RoleDifficultyTurner = "DT";
/// <summary>
/// 表现裁判角色类别编码
/// </summary>
public const string RolePerformance = "P";
/// <summary>
/// 主裁判角色类别编码
/// </summary>
public const string RoleChief = "T";
/// <summary>
/// 评分对象:运动员A(车轮跳花样)
/// </summary>
public const string SubjectA = "A";
/// <summary>
/// 评分对象:运动员B(车轮跳花样)
/// </summary>
public const string SubjectB = "B";
/// <summary>
/// 表现维度的基准分。人工加减与失误自动减都在基准分上调整。
/// </summary>
public const decimal PerformanceBaseValue = 12m;
/// <summary>
/// 表现维度分的上限(下限为 0)
/// </summary>
public const decimal PerformanceMaxValue = 24m;
/// <summary>
/// 车轮跳花样、精英个人花样的表现因子系数:P = average(p1,p2) × 0.05
/// </summary>
public const decimal PerformanceFactorWeight = 0.05m;
/// <summary>
/// 交互绳花样的表现因子系数:P = average(p1,p2) × 0.033
/// </summary>
public const decimal WirePerformanceFactorWeight = 0.033m;
/// <summary>
/// 每缺 1 类规定元素的 Q 扣减系数(花样跳绳打分.xmind 明确规定 Fq=2.5%=0.025):Q = max(0, 1 - 0.025 × 缺失类别数)
/// </summary>
public const decimal RequiredMissingUnit = 0.025m;
/// <summary>
/// 车轮跳花样每类规定元素的要求完成数量。
/// </summary>
public const decimal WheelRequiredCountPerElement = 4m;
/// <summary>
/// 车轮跳花样规定元素应完成总数:4 类 × 每类 4 个。
/// </summary>
public static decimal WheelRequiredTarget => WheelRequiredElements.Count * WheelRequiredCountPerElement;
/// <summary>
/// 摇绳运动员难度取用的动作数(少于 20 个动作时分母仍按 20)
/// </summary>
public const int TurnerTopCount = 20;
/// <summary>
/// 违例、间歇、空间违例的每次扣减值
/// </summary>
public const decimal ViolationUnitDeduction = 0.05m;
/// <summary>
/// 失误的递增扣减序列:第 1 次 0.05、第 2 次 0.075、第 3 次起每次 0.1
/// </summary>
public static readonly decimal[] FailureStepDeductions = { 0.05m, 0.075m, 0.1m };
/// <summary>
/// 难度等级分值表。必须查表取值,不得用浮点公式推导。
/// </summary>
public static IReadOnlyList<DifficultyLevelDefinition> DifficultyLevels { get; } = new List<DifficultyLevelDefinition>
{
new("DIFF_LEVEL_0", "0 级", 0m),
new("DIFF_LEVEL_0_5", "0.5 级", 0.12m),
new("DIFF_LEVEL_1", "1 级", 0.15m),
new("DIFF_LEVEL_2", "2 级", 0.23m),
new("DIFF_LEVEL_3", "3 级", 0.34m),
new("DIFF_LEVEL_4", "4 级", 0.51m),
new("DIFF_LEVEL_5", "5 级", 0.76m),
new("DIFF_LEVEL_6", "6 级", 1.14m),
new("DIFF_LEVEL_7", "7 级", 1.71m),
new("DIFF_LEVEL_8", "8 级", 2.56m)
};
/// <summary>
/// 表现五维。权重合计必须为 1,页面按此顺序展示。
/// </summary>
public static IReadOnlyList<PerformanceDimensionDefinition> PerformanceDimensions { get; } = new List<PerformanceDimensionDefinition>
{
new("MUSIC", "音乐", 0.20m),
new("QUALITY", "完成质量", 0.25m),
new("ENTERTAINMENT", "娱乐", 0.25m),
new("CREATIVITY", "创意", 0.15m),
new("VARIETY", "多样性", 0.15m)
};
/// <summary>
/// 取消比赛资格原因,与裁判端原型逐字一致
/// </summary>
public static IReadOnlyList<DisqualifyReasonDefinition> DisqualifyReasons { get; } = new List<DisqualifyReasonDefinition>
{
new("DQ_DISCIPLINE", "违反赛场纪律"),
new("DQ_MEMBER_COUNT", "参赛人员不符合规定(不足或超过)"),
new("DQ_NO_ROPE_SHOES", "未穿跳绳鞋"),
new("DQ_EXTERNAL_AID", "使用外部助力器材"),
new("DQ_UNCIVILIZED", "比赛中嚼口香糖/不文明用语等"),
new("DQ_ANTI_OLYMPIC", "有悖奥林匹克精神的元素(描绘暴力、战争、宗教、色情有关的元素)"),
new("DQ_LATE_60S", "运动员(队)被叫超过60秒未上场"),
new("DQ_TATTOO", "显露纹身")
};
/// <summary>
/// 车轮跳花样的规定元素类别,只要该类别有动作即计一类完成
/// </summary>
public static IReadOnlyList<RequiredElementDefinition> WheelRequiredElements { get; } = new List<RequiredElementDefinition>
{
new("REQ_STRENGTH_GYMNASTICS", "力量和体操"),
new("REQ_MULTIPLE_ROPE", "多摇"),
new("REQ_ROPE_MANIPULATION", "绳子运用"),
new("REQ_INTERACTION", "互动配合")
};
/// <summary>
/// 个人花样/同步花样裁判席位配置:DP/DM/DR 难度裁判各 1~2 人、表现裁判 1~2 人、主裁 1 人
/// </summary>
public const string SingleJudgeRoleSchemaJson = """
[{"title":"主裁判","slots":[{"code":"T","name":"主裁判-T","min":1,"max":1}]},{"title":"难度裁判","slots":[{"code":"DP","name":"难度裁判-DP(力量体操裁判)","min":1,"max":2},{"code":"DM","name":"难度裁判-DM(多摇裁判)","min":1,"max":2},{"code":"DR","name":"难度裁判-DR(绳子运用裁判)","min":1,"max":2}]},{"title":"表现裁判","slots":[{"code":"P","name":"表现裁判-P","min":1,"max":2}]}]
""";
/// <summary>
/// 车轮跳花样裁判席位配置:运动员A/B难度裁判各 1~2 人、表现裁判 1~2 人、主裁 1 人
/// </summary>
public const string WheelJudgeRoleSchemaJson = """
[{"title":"主裁判","slots":[{"code":"T","name":"主裁判-T","min":1,"max":1}]},{"title":"难度裁判","slots":[{"code":"DA","name":"难度裁判-DA(运动员A难度裁判)","min":1,"max":2},{"code":"DB","name":"难度裁判-DB(运动员B难度裁判)","min":1,"max":2}]},{"title":"表现裁判","slots":[{"code":"P","name":"表现裁判-P","min":1,"max":2}]}]
""";
/// <summary>
/// 交互绳花样裁判席位配置:跳绳者/摇绳者难度裁判各 1~2 人、表现裁判 1~2 人、主裁 1 人
/// </summary>
public const string WireJudgeRoleSchemaJson = """
[{"title":"主裁判","slots":[{"code":"T","name":"主裁判-T","min":1,"max":1}]},{"title":"难度裁判","slots":[{"code":"DJ","name":"难度裁判-DJ(跳绳运动员难度裁判)","min":1,"max":2},{"code":"DT","name":"难度裁判-DT(摇绳运动员难度裁判)","min":1,"max":2}]},{"title":"表现裁判","slots":[{"code":"P","name":"表现裁判-P","min":1,"max":2}]}]
""";
/// <summary>
/// 车轮跳花样计分参数配置。
/// totalMode=factor 表示按 R = D × (1 + P) × Q × M 相乘;difficultyDivisor=2 对应 D=(dA+dB)/2;
/// 主裁同时承担 Q(规定元素缺失数)与 M(扣分合计)两个因子,因此配置了两条 T。
/// </summary>
public const string WheelRuleConfigJson = """
{"ruleType":"FREESTYLE_WHEEL","totalMode":"factor","baseScore":0,"scoreDecimals":2,"difficultyDivisor":2,"correctionNeedApproval":false,"roleAggregations":[{"roleType":"DA","symbol":"dA","label":"运动员A难度","sourceField":"DifficultyScore","targetField":"DifficultyScore","aggregate":"avg","sign":1,"factor":"D","factorWeight":1},{"roleType":"DB","symbol":"dB","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.025},{"roleType":"T","symbol":"M","label":"扣分","sourceField":"DeductionScore","targetField":"DeductionScore","aggregate":"sum","sign":-1,"factor":"M","factorWeight":1}],"tieBreakers":["TotalScore","-DeductionScore","RequiredScore","PerformanceScore","DifficultyScore"]}
""";
/// <summary>
/// 交互绳花样计分参数配置。
/// D = dJ × 1.4 + dT 是加权合并而非取平均,因此 difficultyDivisor 配置为 1;
/// 交互绳总分无 Q 因子,主裁只配置 M。
/// </summary>
public const string WireRuleConfigJson = """
{"ruleType":"FREESTYLE_WIRE","totalMode":"factor","baseScore":0,"scoreDecimals":2,"difficultyDivisor":1,"correctionNeedApproval":false,"roleAggregations":[{"roleType":"DJ","symbol":"dJ","label":"跳绳运动员难度","sourceField":"DifficultyScore","targetField":"DifficultyScore","aggregate":"avg","sign":1,"factor":"D","factorWeight":1.4},{"roleType":"DT","symbol":"dT","label":"摇绳运动员难度","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.033},{"roleType":"T","symbol":"M","label":"扣分","sourceField":"DeductionScore","targetField":"DeductionScore","aggregate":"sum","sign":-1,"factor":"M","factorWeight":1}],"tieBreakers":["TotalScore","-DeductionScore","PerformanceScore","DifficultyScore"]}
""";
/// <summary>
/// 车轮跳花样全部评分项
/// </summary>
public static IReadOnlyList<PrescribedRuleItemDefinition> WheelItems { get; } = BuildWheelItems();
/// <summary>
/// 交互绳花样全部评分项
/// </summary>
public static IReadOnlyList<PrescribedRuleItemDefinition> WireItems { get; } = BuildWireItems();
/// <summary>
/// 构造车轮跳花样评分项:DA/DB 难度等级、表现五维与失误、主裁扣分与规定元素、取消资格原因
/// </summary>
private static List<PrescribedRuleItemDefinition> BuildWheelItems()
{
var items = new List<PrescribedRuleItemDefinition>();
var sort = 0;
// 运动员A、B 的难度裁判各自只看一名运动员,难度明细带 subjectCode 便于后端聚合与定位。
items.AddRange(BuildDifficultyItems(RoleDifficultyA, SubjectA, null, null, null, null, ref sort));
items.AddRange(BuildDifficultyItems(RoleDifficultyB, SubjectB, null, null, null, null, ref sort));
items.AddRange(BuildPerformanceItems(ref sort));
items.AddRange(BuildChiefDeductionItems("T_INTERVAL", "间歇", ref sort));
// 规定元素:4 类元素每类均需完成 4 个,未完成数由服务端算出后参与 Q。
foreach (var element in WheelRequiredElements)
{
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 = WheelRequiredCountPerElement, StepValue = 1m, DefaultValue = 0m,
IsRequired = false, Sort = ++sort,
ExtensionJson = FreestyleItemExtension.Count("个")
});
}
items.Add(new PrescribedRuleItemDefinition
{
JudgeRoleType = RoleChief, ItemCode = "REQUIRED_MISSING", ItemName = "规定元素缺失(个)",
ItemType = ScoreItemType.RequiredElement, ScoreDimension = ScoreDimensionType.None,
CalcMode = ScoreCalcMode.DirectValue,
MinValue = 0m, MaxValue = WheelRequiredTarget, StepValue = 1m, DefaultValue = 0m,
IsRequired = false, Sort = ++sort,
ExtensionJson = FreestyleItemExtension.RequiredMissing(WheelRequiredTarget)
});
items.AddRange(BuildDisqualifyItems(ref sort));
return items;
}
/// <summary>
/// 构造交互绳花样评分项:DJ 按全部动作取均值、DT 取最高 20 个动作,表现与主裁扣分同车轮跳但不计规定元素
/// </summary>
private static List<PrescribedRuleItemDefinition> BuildWireItems()
{
var items = new List<PrescribedRuleItemDefinition>();
var sort = 0;
items.AddRange(BuildDifficultyItems(RoleDifficultyJumper, null, null, Scoring.Dtos.DifficultyAggregateMode.Average, null, null, ref sort));
items.AddRange(BuildDifficultyItems(RoleDifficultyTurner, null, null, Scoring.Dtos.DifficultyAggregateMode.Top, TurnerTopCount, null, ref sort));
items.AddRange(BuildPerformanceItems(ref sort));
items.AddRange(BuildChiefDeductionItems("T_SPACE_VIOLATION", "空间违例", ref sort));
items.AddRange(BuildDisqualifyItems(ref sort));
return items;
}
/// <summary>
/// 构造一个难度裁判类型的等级计数项。
/// aggregate 为空表示按数量 × 等级分值直接累加;avg 按动作总数取均值;top 取最高 topCount 个动作。
/// itemCodePrefix 用于表演赛把同一裁判的多个类别拆成独立评分项(如 SR_DIFF_LEVEL_3),避免编码互相覆盖。
/// </summary>
internal static List<PrescribedRuleItemDefinition> BuildDifficultyItems(string roleType, string? subjectCode,
string? subjectName, string? aggregate, int? topCount, string? itemCodePrefix, ref int sort,
IReadOnlyList<DifficultyLevelDefinition>? difficultyLevels = null)
{
var items = new List<PrescribedRuleItemDefinition>();
foreach (var level in difficultyLevels ?? DifficultyLevels)
{
items.Add(new PrescribedRuleItemDefinition
{
JudgeRoleType = roleType,
ItemCode = string.IsNullOrEmpty(itemCodePrefix) ? level.ItemCode : $"{itemCodePrefix}_{level.ItemCode}",
ItemName = level.LevelName,
ItemType = ScoreItemType.DifficultyCount, ScoreDimension = ScoreDimensionType.Difficulty,
CalcMode = ScoreCalcMode.CountByUnit,
UnitValue = level.LevelValue, MinValue = 0m, MaxValue = 50m, StepValue = 1m, DefaultValue = 0m,
IsRequired = false, Sort = ++sort,
ExtensionJson = FreestyleItemExtension.Difficulty(subjectCode, subjectName, aggregate, topCount)
});
}
return items;
}
/// <summary>
/// 构造表现裁判评分项:五维各一组人工加/减项 + 一个只读维度分自动项,再加一个失误次数项。
/// 失误联动五维自动减分由服务端在提交时重算,客户端只能改失误次数。
/// </summary>
internal static List<PrescribedRuleItemDefinition> BuildPerformanceItems(ref int sort)
{
var items = new List<PrescribedRuleItemDefinition>();
foreach (var dimension in PerformanceDimensions)
{
items.Add(new PrescribedRuleItemDefinition
{
JudgeRoleType = RolePerformance, ItemCode = $"{dimension.Code}_PLUS", ItemName = $"{dimension.Name}(加数量)",
ItemType = ScoreItemType.Performance, ScoreDimension = ScoreDimensionType.Performance,
CalcMode = ScoreCalcMode.CountByUnit,
UnitValue = 1m, MinValue = 0m, MaxValue = 50m, StepValue = 1m, DefaultValue = 0m,
IsRequired = false, Sort = ++sort,
ExtensionJson = FreestyleItemExtension.PerformanceAdjust(dimension.Code, 1)
});
items.Add(new PrescribedRuleItemDefinition
{
JudgeRoleType = RolePerformance, ItemCode = $"{dimension.Code}_MINUS", ItemName = $"{dimension.Name}(减数量)",
ItemType = ScoreItemType.Performance, ScoreDimension = ScoreDimensionType.Performance,
CalcMode = ScoreCalcMode.CountByUnit,
UnitValue = 1m, MinValue = 0m, MaxValue = 50m, StepValue = 1m, DefaultValue = 0m,
IsRequired = false, Sort = ++sort,
ExtensionJson = FreestyleItemExtension.PerformanceAdjust(dimension.Code, -1)
});
items.Add(new PrescribedRuleItemDefinition
{
JudgeRoleType = RolePerformance, ItemCode = $"{dimension.Code}_SCORE", ItemName = dimension.Name,
ItemType = ScoreItemType.Performance, ScoreDimension = ScoreDimensionType.Performance,
CalcMode = ScoreCalcMode.DirectValue,
UnitValue = 1m, MinValue = 0m, MaxValue = PerformanceMaxValue, StepValue = 1m,
DefaultValue = PerformanceBaseValue, IsRequired = false, Sort = ++sort,
ExtensionJson = FreestyleItemExtension.PerformanceDimension(dimension.Code, dimension.Weight)
});
}
items.Add(new PrescribedRuleItemDefinition
{
JudgeRoleType = RolePerformance, ItemCode = "FAILURE_COUNT", ItemName = "失误次数",
ItemType = ScoreItemType.Performance, ScoreDimension = ScoreDimensionType.None,
CalcMode = ScoreCalcMode.CountByUnit,
UnitValue = 1m, MinValue = 0m, MaxValue = 50m, StepValue = 1m, DefaultValue = 0m,
IsRequired = false, Sort = ++sort,
ExtensionJson = FreestyleItemExtension.FailureCount()
});
return items;
}
/// <summary>
/// 构造主裁扣分项。失误按递增序列扣减,违例与 extraCode 指定的第三项(车轮跳为间歇、交互绳为空间违例)按每次 0.05 扣减。
/// extraCode 为空时只生成失误与违例两项(表演赛)。
/// </summary>
internal static List<PrescribedRuleItemDefinition> BuildChiefDeductionItems(string? extraCode, string? extraName, ref int sort)
{
var items = new List<PrescribedRuleItemDefinition>
{
new()
{
JudgeRoleType = RoleChief, ItemCode = "T_FAILURE", ItemName = "失误",
ItemType = ScoreItemType.Deduction, ScoreDimension = ScoreDimensionType.Deduction,
CalcMode = ScoreCalcMode.CountByUnit,
UnitValue = FailureStepDeductions[0], MinValue = 0m, MaxValue = 50m, StepValue = 1m, DefaultValue = 0m,
IsRequired = false, Sort = ++sort,
ExtensionJson = FreestyleItemExtension.StepDeduction(FailureStepDeductions)
},
new()
{
JudgeRoleType = RoleChief, ItemCode = "T_VIOLATION", ItemName = "违例",
ItemType = ScoreItemType.Deduction, ScoreDimension = ScoreDimensionType.Deduction,
CalcMode = ScoreCalcMode.CountByUnit,
UnitValue = ViolationUnitDeduction, MinValue = 0m, MaxValue = 50m, StepValue = 1m, DefaultValue = 0m,
IsRequired = false, Sort = ++sort,
ExtensionJson = FreestyleItemExtension.Count("次")
}
};
if (string.IsNullOrEmpty(extraCode)) return items;
items.Add(new PrescribedRuleItemDefinition
{
JudgeRoleType = RoleChief, ItemCode = extraCode, ItemName = extraName ?? extraCode,
ItemType = ScoreItemType.Deduction, ScoreDimension = ScoreDimensionType.Deduction,
CalcMode = ScoreCalcMode.CountByUnit,
UnitValue = ViolationUnitDeduction, MinValue = 0m, MaxValue = 50m, StepValue = 1m, DefaultValue = 0m,
IsRequired = false, Sort = ++sort,
ExtensionJson = FreestyleItemExtension.Count("次")
});
return items;
}
/// <summary>
/// 构造主裁的取消比赛资格原因项,勾选任一项即整条成绩按 0 分处理
/// </summary>
internal static List<PrescribedRuleItemDefinition> BuildDisqualifyItems(ref int sort)
{
var items = new List<PrescribedRuleItemDefinition>();
foreach (var reason in DisqualifyReasons)
{
items.Add(new PrescribedRuleItemDefinition
{
JudgeRoleType = RoleChief, ItemCode = reason.ItemCode, ItemName = reason.ReasonName,
ItemType = ScoreItemType.Disqualify, ScoreDimension = ScoreDimensionType.None,
CalcMode = ScoreCalcMode.RecordOnly,
IsRequired = false, Sort = ++sort
});
}
return items;
}
}
/// <summary>
/// 一个难度等级的定义
/// </summary>
/// <param name="ItemCode">评分项编码</param>
/// <param name="LevelName">等级名称</param>
/// <param name="LevelValue">等级分值</param>
public record DifficultyLevelDefinition(string ItemCode, string LevelName, decimal LevelValue);
/// <summary>
/// 一个表现维度的定义
/// </summary>
/// <param name="Code">维度编码</param>
/// <param name="Name">维度名称</param>
/// <param name="Weight">在单裁判加权分里的权重</param>
public record PerformanceDimensionDefinition(string Code, string Name, decimal Weight);
/// <summary>
/// 一条取消比赛资格原因的定义
/// </summary>
/// <param name="ItemCode">评分项编码</param>
/// <param name="ReasonName">原因名称</param>
public record DisqualifyReasonDefinition(string ItemCode, string ReasonName);
/// <summary>
/// 一类规定元素的定义
/// </summary>
/// <param name="ItemCode">评分项编码</param>
/// <param name="ItemName">类别名称</param>
public record RequiredElementDefinition(string ItemCode, string ItemName);
/// <summary>
/// 花样赛评分项扩展配置(ScoreRuleItem.ExtensionJson)的构造。
/// 字段名与 ScoreSheetCalculationService.CreateDefinition 的解析逻辑一一对应。
/// </summary>
public static class FreestyleItemExtension
{
/// <summary>
/// 难度等级计数项:subjectCode/subjectName 区分同一裁判要评的多个对象(车轮跳的两名运动员、表演赛的多个器械类别),
/// difficultyAggregate 指定聚合方式
/// </summary>
public static string Difficulty(string? subjectCode, string? subjectName, string? aggregate, int? topCount)
{
var node = new JObject { ["inputMode"] = "count", ["unitLabel"] = "个" };
if (!string.IsNullOrEmpty(subjectCode)) node["subjectCode"] = subjectCode;
if (!string.IsNullOrEmpty(subjectName)) node["subjectName"] = subjectName;
if (!string.IsNullOrEmpty(aggregate)) node["difficultyAggregate"] = aggregate;
if (topCount is > 0) node["difficultyTopCount"] = topCount.Value;
return node.ToString(Formatting.None);
}
/// <summary>
/// 表现维度的人工加/减项:adjustSign 为 1 表示每次计数使维度分 +1,-1 表示 -1
/// </summary>
public static string PerformanceAdjust(string dimensionCode, int adjustSign) =>
new JObject
{
["inputMode"] = "count", ["unitLabel"] = "次",
["dimensionCode"] = dimensionCode, ["adjustSign"] = adjustSign
}.ToString(Formatting.None);
/// <summary>
/// 表现维度分(只读自动项):基准 12 分,按权重参与单裁判加权分
/// </summary>
public static string PerformanceDimension(string dimensionCode, decimal weight) =>
new JObject
{
["inputMode"] = "score", ["isAuto"] = true,
["dimensionCode"] = dimensionCode, ["dimensionWeight"] = weight,
["baseValue"] = FreestyleRuleDefinition.PerformanceBaseValue
}.ToString(Formatting.None);
/// <summary>
/// 失误次数项:每次失误使五个维度的自动减分各 +1
/// </summary>
public static string FailureCount() =>
new JObject
{
["inputMode"] = "count", ["unitLabel"] = "次", ["isFailureCount"] = true,
["remark"] = "表现裁判记一次失误,其对应的表现五个方面(音乐,完成质量,创意,娱乐和多样性)减数量+1"
}.ToString(Formatting.None);
/// <summary>
/// 递增扣分项:第 1 次 0.05、第 2 次 0.075、第 3 次起每次 0.1
/// </summary>
public static string StepDeduction(IEnumerable<decimal> steps) =>
new JObject
{
["inputMode"] = "count", ["unitLabel"] = "次",
["stepDeductions"] = new JArray(steps.Cast<object>().ToArray()),
["remark"] = "第1次扣0.05,第2次扣0.075,第3次起每次扣0.1"
}.ToString(Formatting.None);
/// <summary>
/// 普通计数项
/// </summary>
public static string Count(string unitLabel) =>
new JObject { ["inputMode"] = "count", ["unitLabel"] = unitLabel }.ToString(Formatting.None);
/// <summary>
/// 规定元素类别项:录入数量大于 0 即计一类完成(表演赛使用)。
/// </summary>
public static string RequiredElement() =>
new JObject
{
["inputMode"] = "count", ["unitLabel"] = "个", ["requiredAsCategory"] = true
}.ToString(Formatting.None);
/// <summary>
/// 规定元素缺失数(只读自动项):缺失数 = max(0, 目标类别数 - 完成类别数)
/// </summary>
public static string RequiredMissing(decimal target) =>
new JObject
{
["inputMode"] = "score", ["isAuto"] = true, ["unitLabel"] = "个", ["requiredTarget"] = target
}.ToString(Formatting.None);
}