using YD_Event.Application.Scoring.Dtos;
namespace YD_Event.Application.Scoring;
///
/// 计分规则配置解析服务
///
public static class ScoreRuleConfigService
{
///
/// 解析计分规则配置,配置缺失或格式不合法时回退到规定赛的默认配置,避免整场比赛因一处配置错误无法计分。
///
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;
config.TotalMode = (root["totalMode"]?.ToString() ?? config.TotalMode).Trim().ToLowerInvariant();
config.BaseScore = root["baseScore"]?.Value() ?? config.BaseScore;
config.ScoreDecimals = Math.Clamp(root["scoreDecimals"]?.Value() ?? config.ScoreDecimals, 0, 4);
config.DeductionUnit = root["deductionUnit"]?.Value() ?? config.DeductionUnit;
config.DifficultyDivisor = ReadDecimal(root["difficultyDivisor"]);
config.CorrectionNeedApproval = root["correctionNeedApproval"]?.Value() ?? 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(),
AggregateDecimals = ReadInt(node["aggregateDecimals"], 0, 4),
Sign = ReadSign(node["sign"]),
Factor = ReadFactor(node["factor"]),
FactorWeight = ReadDecimal(node["factorWeight"]) ?? 1m
});
}
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;
ApplyBugRuleOverrides(config);
return Fallback(config);
}
/// 个人花样与同步花样采用相同的表现分计算口径。
public static bool IsIndividualFamily(string ruleType) =>
ruleType.ToUpperInvariant() is "FREESTYLE_SINGLE" or "FREESTYLE_DOUBLE_SYNC" or "FREESTYLE_QUAD_SYNC";
/// 补齐花样赛规则的默认计算参数。解析过程只生成内存配置,不改写数据库。
private static void ApplyBugRuleOverrides(ScoreRuleConfig config)
{
if (!config.IsFactorTotal) return;
foreach (var role in config.RoleAggregations.Where(x => x.Factor == ScoreFactorCode.Required))
{
if (IsIndividualFamily(config.RuleType))
{
// 个人花样的每类规定元素缺失数先按同类型裁判取平均,再四舍五入为整数参与 Q 计算。
// 同步花样保留半个规定元素的精度。
role.AggregateDecimals = string.Equals(config.RuleType, "FREESTYLE_SINGLE",
StringComparison.OrdinalIgnoreCase) ? 0 : null;
}
if (config.RuleType.StartsWith("FREESTYLE_WHEEL", StringComparison.OrdinalIgnoreCase))
role.FactorWeight = 0.025m;
}
}
///
/// 读取参与总分的符号,非法值按加分处理
///
private static int ReadSign(JToken? node)
{
try { return node?.Value() == -1 ? -1 : 1; }
catch (Exception ex) when (ex is FormatException or InvalidCastException or OverflowException) { return 1; }
}
///
/// 读取乘法公式的因子编码,未配置或不是 D/P/Q/M 时返回 null(该裁判类型不参与因子计算)
///
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;
}
///
/// 读取配置里的数值,非数字等非法值按未配置处理
///
private static decimal? ReadDecimal(JToken? node)
{
try { return node?.Value(); }
catch (Exception ex) when (ex is FormatException or InvalidCastException or OverflowException) { return null; }
}
///
/// 读取受限整数配置,非法值按未配置处理。
///
private static int? ReadInt(JToken? node, int min, int max)
{
try
{
var value = node?.Value();
return value.HasValue ? Math.Clamp(value.Value, min, max) : null;
}
catch (Exception ex) when (ex is FormatException or InvalidCastException or OverflowException)
{
return null;
}
}
///
/// 未配置聚合方式时按规定赛的 T1/T2/T3 兜底
///
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;
}
}