update:计分规则修正

This commit is contained in:
ltx 2026-09-28 16:23:07 +08:00
parent f4346d30cd
commit f5f751eb63

View File

@ -173,7 +173,7 @@ public static class ScoreSheetCalculationService
/// <summary>
/// 按计分规则计算一张评分单。
/// requireAll 为 true(正式提交)时校验必填项与取值范围;为 false(保存草稿)时允许留空,只算已录入的部分。
/// 花样赛的表现维度分、失误自动减分、规定元素缺失数一律由服务端联动生成,客户端传来的自动项不参与计算。
/// 花样赛的表现维度分、失误联动项、规定元素缺失数一律由服务端生成,客户端传来的自动项不参与计算。
/// </summary>
public static ScoreSheetCalculationResult Calculate(List<ScoreItemDefinition> definitions,
List<ScoreItemInputValue> inputs, ScoreRuleConfig config, bool requireAll)
@ -213,8 +213,7 @@ public static class ScoreSheetCalculationService
// 表现维度的加减项、递增扣分项、规定元素计数项各有专门算法,不按小计直接累加。
if (definition.DimensionCode != null && definition.AdjustSign != 0) continue;
if (definition.StepDeductions is { Count: > 0 }) continue;
if (definition.Item.ScoreDimension == ScoreDimensionCode.Required
&& definition.Item.ItemType == ScoreItemTypeCode.RequiredElement) continue;
if (definition.Item.ItemType == ScoreItemTypeCode.RequiredElement) continue;
switch (definition.Item.ScoreDimension)
{
@ -239,11 +238,17 @@ public static class ScoreSheetCalculationService
result.RequiredScore = config.Round(result.RequiredScore);
result.DeductionScore = config.Round(result.DeductionScore);
}
// 花样赛的规定元素完成量只用于跨裁判聚合时计算 Q,不能加进单张评分单的本裁判得分。
// 最终 R 仍由跨裁判聚合按 D×(1+P)×Q×M 计算;规定赛保留原有加总口径。
// 难度裁判的规定元素完成量仅作为独立记录,不能计入本裁判难度分。
// 同时兼容尚未配置 factor 模式的个人花样规则;规定赛完成质量裁判仍保留 RequiredScore 加总。
var isDifficultySheet = definitions.Any(x => !x.IsAuto &&
x.Item.ItemType != ScoreItemTypeCode.RequiredElement &&
(x.Item.ItemType == ScoreItemTypeCode.DifficultyCount ||
x.Item.ScoreDimension == ScoreDimensionCode.Difficulty));
var requiredContribution = isDifficultySheet ? 0m : result.RequiredScore;
// 花样赛最终 R 仍由跨裁判聚合按 D×(1+P)×Q×M 计算。
result.TotalScore = config.IsFactorTotal
? config.Round(result.DifficultyScore + result.PerformanceScore)
: config.Round(result.DifficultyScore + result.PerformanceScore + result.RequiredScore - result.DeductionScore);
: config.Round(result.DifficultyScore + result.PerformanceScore + requiredContribution - result.DeductionScore);
return result;
}
@ -257,7 +262,9 @@ public static class ScoreSheetCalculationService
var mode = definitions.Select(x => x.DifficultyAggregate).FirstOrDefault(x => !string.IsNullOrEmpty(x));
if (mode == null) return;
var difficultyLines = result.Lines
.Where(x => x.ScoreDimension == ScoreDimensionCode.Difficulty && x.CountValue is > 0m).ToList();
.Where(x => x.ItemType != ScoreItemTypeCode.RequiredElement &&
x.ScoreDimension == ScoreDimensionCode.Difficulty &&
x.CountValue is > 0m).ToList();
var actionCount = difficultyLines.Sum(x => x.CountValue!.Value);
if (actionCount <= 0m)
{
@ -296,7 +303,8 @@ public static class ScoreSheetCalculationService
}
/// <summary>
/// 表现维度计算。页面减数量 = 人工减 + 失误自动减;按规则公式还需单独减失误次数。
/// 表现维度计算。个人花样的失误单独参与总成绩扣分,不进入表现分;
/// 其他花样项目沿用失误联动表现维度的既有口径。
/// 每个维度 = 基准分 + 人工加 - (人工减 + 失误自动减) - 失误次数,限制在上下限内,
/// 再按各维度权重加权求和得到本裁判的表现分。失误自动减分作为自动项落明细,裁判不可直接修改。
/// </summary>
@ -308,11 +316,16 @@ public static class ScoreSheetCalculationService
.ToList();
if (dimensions.Count == 0) return;
// 失误数联动五个维度各自动减 1 次。
// 个人花样的失误由成绩汇总中的 M 系数单独处理,不能再降低本裁判的表现分。
var failure = definitions.FirstOrDefault(x => x.IsFailureCount);
var failureCount = failure == null
? 0m
: result.Lines.FirstOrDefault(x => string.Equals(x.ItemCode, failure.ItemCode, StringComparison.OrdinalIgnoreCase))?.CountValue ?? 0m;
var isIndividualFreestyle = string.Equals(config.RuleType, "FREESTYLE_SINGLE", StringComparison.OrdinalIgnoreCase);
var performanceFailureCount = isIndividualFreestyle ? 0m : failureCount;
var hasPerformanceMarks = definitions.Any(x => !x.IsAuto && x.AdjustSign != 0 &&
result.Lines.Any(line => string.Equals(line.ItemCode, x.ItemCode, StringComparison.OrdinalIgnoreCase)
&& line.CountValue is > 0m));
var weighted = 0m;
foreach (var dimension in dimensions)
@ -328,10 +341,12 @@ public static class ScoreSheetCalculationService
.FirstOrDefault(x => string.Equals(x.ItemCode, adjust.ItemCode, StringComparison.OrdinalIgnoreCase))?.CountValue ?? 0m;
raw += adjust.AdjustSign * count * (adjust.Item.UnitValue ?? 1m);
}
// 失误同时增加页面减数量,并作为公式中的独立失误项再扣一次。
raw -= failureCount * 2m * (dimension.Item.UnitValue ?? 1m);
// 其他花样项目沿用失误联动;个人花样这里不扣,汇总时由 M 系数处理。
raw -= performanceFailureCount * 2m * (dimension.Item.UnitValue ?? 1m);
// 维度分限制在规则给出的上下限内(规程为 0~24)并取整。
var value = Math.Clamp(Math.Round(raw, 0, MidpointRounding.AwayFromZero), dimension.MinValue, dimension.MaxValue);
var value = isIndividualFreestyle && !hasPerformanceMarks
? 0m
: Math.Clamp(Math.Round(raw, 0, MidpointRounding.AwayFromZero), dimension.MinValue, dimension.MaxValue);
weighted += value * dimension.DimensionWeight!.Value;
result.Lines.Add(new ScoreSheetLine
@ -341,12 +356,16 @@ public static class ScoreSheetCalculationService
InputValue = value, UnitValue = dimension.Item.UnitValue, Subtotal = value,
Sort = dimension.Item.Sort, IsAutoGenerated = true,
SubjectCode = dimension.SubjectCode,
ParentItemCode = failure?.ItemCode
ParentItemCode = performanceFailureCount > 0m ? failure?.ItemCode : null
});
result.Steps.Add(new ScoreSheetStep
{
Code = $"DIM_{dimension.DimensionCode}", Label = dimension.Item.ItemName,
Formula = $"{Trim(dimension.BaseValue ?? 0m)} + 人工加 - (人工减 + 失误联动减{Trim(failureCount)}) - 失误{Trim(failureCount)}次",
Formula = isIndividualFreestyle && !hasPerformanceMarks
? "尚未录入表现加减,表现分为 0;失误单独计入扣分"
: isIndividualFreestyle
? $"{Trim(dimension.BaseValue ?? 0m)} + 人工加 - 人工减(失误单独计入扣分)"
: $"{Trim(dimension.BaseValue ?? 0m)} + 人工加 - (人工减 + 失误联动减{Trim(performanceFailureCount)}) - 失误{Trim(performanceFailureCount)}次",
Value = value
});
}