YD_Event.Admin/YD_Event.Application/Scoring/ScoreRuleConfigService.cs

109 lines
5.3 KiB
C#
Raw Normal View History

2026-08-22 20:34:05 +08:00
using YD_Event.Application.Scoring.Dtos;
namespace YD_Event.Application.Scoring;
/// <summary>
/// 计分规则配置解析服务
/// </summary>
public static class ScoreRuleConfigService
{
/// <summary>
/// 解析计分规则配置,配置缺失或格式不合法时回退到规定赛的默认配置,避免整场比赛因一处配置错误无法计分。
/// </summary>
public static ScoreRuleConfig Parse(string? configJson)
{
var config = new ScoreRuleConfig();
if (string.IsNullOrWhiteSpace(configJson)) return Fallback(config);
JObject root;
try { root = JObject.Parse(configJson); } catch (JsonException) { return Fallback(config); }
// 配置由人工维护,字段写成非数字等非法值时按默认值处理,不能让整场比赛无法计分。
try
{
config.RuleType = root["ruleType"]?.ToString() ?? config.RuleType;
2026-08-23 16:21:49 +08:00
config.TotalMode = (root["totalMode"]?.ToString() ?? config.TotalMode).Trim().ToLowerInvariant();
2026-08-22 20:34:05 +08:00
config.BaseScore = root["baseScore"]?.Value<decimal?>() ?? config.BaseScore;
config.ScoreDecimals = Math.Clamp(root["scoreDecimals"]?.Value<int?>() ?? config.ScoreDecimals, 0, 4);
config.DeductionUnit = root["deductionUnit"]?.Value<decimal?>() ?? config.DeductionUnit;
2026-08-23 16:21:49 +08:00
config.DifficultyDivisor = ReadDecimal(root["difficultyDivisor"]);
2026-08-22 20:34:05 +08:00
config.CorrectionNeedApproval = root["correctionNeedApproval"]?.Value<bool?>() ?? false;
}
catch (Exception ex) when (ex is FormatException or InvalidCastException or OverflowException)
{
return Fallback(new ScoreRuleConfig());
}
foreach (var node in root["roleAggregations"] as JArray ?? new JArray())
{
var roleType = node["roleType"]?.ToString();
if (string.IsNullOrWhiteSpace(roleType)) continue;
config.RoleAggregations.Add(new RoleAggregationConfig
{
RoleType = roleType!.Trim().ToUpperInvariant(),
Symbol = node["symbol"]?.ToString() ?? roleType!,
Label = node["label"]?.ToString() ?? roleType!,
SourceField = node["sourceField"]?.ToString() ?? nameof(EventJudgeScoreSheet.TotalScore),
TargetField = node["targetField"]?.ToString() ?? node["sourceField"]?.ToString() ?? nameof(EventResult.TotalScore),
Aggregate = (node["aggregate"]?.ToString() ?? ScoreRuleConfig.AggregateAvg).Trim().ToLowerInvariant(),
2026-08-23 16:21:49 +08:00
Sign = ReadSign(node["sign"]),
Factor = ReadFactor(node["factor"]),
FactorWeight = ReadDecimal(node["factorWeight"]) ?? 1m
2026-08-22 20:34:05 +08:00
});
}
var tieBreakers = (root["tieBreakers"] as JArray ?? new JArray())
.Select(x => x.ToString()).Where(x => !string.IsNullOrWhiteSpace(x)).ToList();
if (tieBreakers.Count > 0) config.TieBreakers = tieBreakers;
return Fallback(config);
}
/// <summary>
/// 读取参与总分的符号,非法值按加分处理
/// </summary>
private static int ReadSign(JToken? node)
{
try { return node?.Value<int?>() == -1 ? -1 : 1; }
catch (Exception ex) when (ex is FormatException or InvalidCastException or OverflowException) { return 1; }
}
2026-08-23 16:21:49 +08:00
/// <summary>
/// 读取乘法公式的因子编码,未配置或不是 D/P/Q/M 时返回 null(该裁判类型不参与因子计算)
/// </summary>
private static string? ReadFactor(JToken? node)
{
var factor = node?.ToString()?.Trim().ToUpperInvariant();
if (string.IsNullOrEmpty(factor)) return null;
return factor is ScoreFactorCode.Difficulty or ScoreFactorCode.Performance
or ScoreFactorCode.Required or ScoreFactorCode.Deduction ? factor : null;
}
/// <summary>
/// 读取配置里的数值,非数字等非法值按未配置处理
/// </summary>
private static decimal? ReadDecimal(JToken? node)
{
try { return node?.Value<decimal?>(); }
catch (Exception ex) when (ex is FormatException or InvalidCastException or OverflowException) { return null; }
}
2026-08-22 20:34:05 +08:00
/// <summary>
/// 未配置聚合方式时按规定赛的 T1/T2/T3 兜底
/// </summary>
private static ScoreRuleConfig Fallback(ScoreRuleConfig config)
{
if (config.RoleAggregations.Count > 0) return config;
config.RoleAggregations.AddRange(new[]
{
new RoleAggregationConfig { RoleType = "CJ", Symbol = "T1", Label = "创意分",
SourceField = nameof(EventJudgeScoreSheet.PerformanceScore), TargetField = nameof(EventResult.PerformanceScore), Aggregate = ScoreRuleConfig.AggregateAvg, Sign = 1 },
new RoleAggregationConfig { RoleType = "CQ", Symbol = "T2", Label = "完成分",
SourceField = nameof(EventJudgeScoreSheet.RequiredScore), TargetField = nameof(EventResult.RequiredScore), Aggregate = ScoreRuleConfig.AggregateAvg, Sign = 1 },
new RoleAggregationConfig { RoleType = "T", Symbol = "T3", Label = "扣分",
SourceField = nameof(EventJudgeScoreSheet.DeductionScore), TargetField = nameof(EventResult.DeductionScore), Aggregate = ScoreRuleConfig.AggregateSum, Sign = -1 }
});
return config;
}
}