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 = 0; } 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; } }