using YD_Event.Application.Event.Dtos; namespace YD_Event.Application.Scoring; /// /// 计分规则里裁判席位配置(JudgeRoleSchemaJson)的解析工具。 /// 裁判分配与裁判端评分都要按同一份配置判断裁判类型与执裁资格,因此集中在这里,避免两处规则走偏。 /// public static class JudgeRoleSchemaService { /// /// 主裁判 /// public const string RoleChief = "T"; /// /// 表现裁判 /// public const string RolePerformance = "P"; /// /// 创意编排裁判 /// public const string RoleChoreography = "CJ"; /// /// 完成质量裁判 /// public const string RoleCompletionQuality = "CQ"; /// /// 解析计分规则的裁判席位配置。 /// 配置中的每个 slot 代表一个裁判类型(DP、DM、DR、P、T 等),可分配多名裁判; /// 主裁固定 1 人,其余类型分配时不限人数(成绩按平均值计算),配置里显式给出 min/max 时以配置为准。 /// public static List ParseSections(string? schemaJson) { var sections = new List(); if (string.IsNullOrWhiteSpace(schemaJson)) return sections; JArray array; // 规则配置由人工维护,格式不合法时按“未配置裁判类型”处理,由调用方给出提示。 try { array = JArray.Parse(schemaJson); } catch (JsonException) { return sections; } var seen = new HashSet(StringComparer.OrdinalIgnoreCase); foreach (var node in array) { var section = new JudgeRoleSectionOutput { Title = node["title"]?.ToString() ?? node["name"]?.ToString() ?? "裁判" }; foreach (var slotNode in node["slots"] as JArray ?? new JArray()) { var code = slotNode["code"]?.ToString(); if (string.IsNullOrWhiteSpace(code)) continue; var roleType = NormalizeRoleType(code); // 同一类型在配置里重复出现(如历史 DP1/DP2 席位)时只保留一条,人数改由分配时决定。 if (!seen.Add(roleType)) continue; var categories = (slotNode["categories"] as JArray)? .Select(c => c.ToString()) .Where(s => !string.IsNullOrWhiteSpace(s)) .ToList(); if (categories == null || categories.Count == 0) { categories = roleType switch { "DGA" => new List { "DP+DM (个人绳)", "DA (车轮跳)", "DT (交互绳)", "DT (旅行跳)" }, "DGB" => new List { "DR (个人绳)", "DB (车轮跳)", "DJ (交互绳)", "DL (旅行跳)" }, _ => new List() }; } section.Slots.Add(new JudgeRoleSlotOutput { Code = roleType, Name = slotNode["name"]?.ToString() ?? RoleName(roleType), // 未配置 min 时至少要有 1 人;未配置 max 时只有主裁限 1 人,其余类型不限。 MinCount = Math.Max(ReadCount(slotNode["min"]) ?? 1, 0), MaxCount = ReadCount(slotNode["max"]) ?? (roleType == RoleChief ? (int?)1 : null), RoleFlag = RoleFlagOf(roleType), Categories = categories }); } if (section.Slots.Count > 0) sections.Add(section); } return sections; } /// /// 读取席位人数配置,非数字等非法值按未配置处理 /// private static int? ReadCount(JToken? node) { try { return node?.Value(); } catch (Exception ex) when (ex is FormatException or InvalidCastException or OverflowException) { return null; } } /// /// 取配置里的所有裁判类型(按配置顺序去重) /// public static List ParseSlots(string? schemaJson) => ParseSections(schemaJson).SelectMany(x => x.Slots).ToList(); /// /// 把席位编码归一为裁判类型编码,兼容 DP1、CJ2 这类带席位序号的编码 /// public static string NormalizeRoleType(string? code) { var trimmed = (code ?? string.Empty).Trim().ToUpperInvariant(); var letters = new string(trimmed.TakeWhile(ch => !char.IsDigit(ch)).ToArray()); return letters.Length > 0 ? letters : trimmed; } /// /// 裁判类型编码对应的执裁资格位,未单独列出的类型都按难度裁判处理 /// public static int RoleFlagOf(string roleType) => roleType switch { RoleChief => (int)RefereeRole.Chief, RolePerformance => (int)RefereeRole.Performance, RoleChoreography => (int)RefereeRole.Choreography, RoleCompletionQuality => (int)RefereeRole.CompletionQuality, _ => (int)RefereeRole.Difficulty }; /// /// 裁判类型的默认名称,规则配置里没写 name 时用于展示 /// public static string RoleName(string roleType) => roleType switch { RoleChief => "主裁判", RolePerformance => "表现裁判", RoleChoreography => "创意编排裁判", RoleCompletionQuality => "完成质量裁判", _ => "难度裁判" }; /// /// 取裁判类型在当前计分规则里的名称,规则里没配这个类型时回退到默认名称 /// public static string RoleNameOf(List? slots, string? judgeRoleType) { var roleType = NormalizeRoleType(judgeRoleType); var matched = slots?.FirstOrDefault(x => string.Equals(x.Code, roleType, StringComparison.OrdinalIgnoreCase)); return matched?.Name ?? RoleName(roleType); } }