531 lines
26 KiB
C#
531 lines
26 KiB
C#
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 扣减系数:Q = max(0, 1 - 0.05 × 缺失类别数)
|
||
/// </summary>
|
||
public const decimal RequiredMissingUnit = 0.05m;
|
||
|
||
/// <summary>
|
||
/// 车轮跳花样的规定元素目标类别数(力量和体操、多摇、绳子运用、互动配合)
|
||
/// </summary>
|
||
public const decimal WheelRequiredTarget = 4m;
|
||
|
||
/// <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>
|
||
/// 车轮跳花样裁判席位配置:运动员A/B难度裁判各 2 人、表现裁判 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":2},{"code":"DB","name":"难度裁判-DB(运动员B难度裁判)","min":2}]},{"title":"表现裁判","slots":[{"code":"P","name":"表现裁判-P","min":2}]}]
|
||
""";
|
||
|
||
/// <summary>
|
||
/// 交互绳花样裁判席位配置:跳绳者/摇绳者难度裁判各 2 人、表现裁判 2 人、主裁 1 人
|
||
/// </summary>
|
||
public const string WireJudgeRoleSchemaJson = """
|
||
[{"title":"主裁判","slots":[{"code":"T","name":"主裁判-T","min":1,"max":1}]},{"title":"难度裁判","slots":[{"code":"DJ","name":"难度裁判-DJ(跳绳运动员难度裁判)","min":2},{"code":"DT","name":"难度裁判-DT(摇绳运动员难度裁判)","min":2}]},{"title":"表现裁判","slots":[{"code":"P","name":"表现裁判-P","min":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.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>
|
||
/// 交互绳花样计分参数配置。
|
||
/// 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));
|
||
|
||
// 规定元素:每类只要有动作即计一类完成,缺失类别数由服务端算出后参与 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 = 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 = 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)
|
||
{
|
||
var items = new List<PrescribedRuleItemDefinition>();
|
||
foreach (var level in 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);
|
||
}
|