using System.Text.Json; using MAF1.Agents; namespace MAF1.Route; /// /// 方案 C 的图:节点 = 主管能调的专家白名单;边 = 允许的交接。 /// 没有边时,名单上的节点可以按任意顺序调用。 /// public sealed class RouteGraph { public List Nodes { get; set; } = []; public List Edges { get; set; } = []; public bool HasEdges => Edges.Count > 0; /// 主管第一跳只能到这些节点:无边时整张白名单;有边时取没有入边的入口。 public IReadOnlyList EntryNodes() { if (!HasEdges) { return Nodes; } HashSet targets = new(Edges.Select(e => e.To), StringComparer.OrdinalIgnoreCase); List sources = Nodes.Where(n => !targets.Contains(n.Id)).ToList(); return sources.Count > 0 ? sources : Nodes; } public bool HasOutgoing(string nodeId) => Edges.Any(e => e.From.Equals(nodeId, StringComparison.OrdinalIgnoreCase)); public RouteNode? Find(string nodeId) => Nodes.FirstOrDefault(n => n.Id.Equals(nodeId, StringComparison.OrdinalIgnoreCase)); public static JsonSerializerOptions JsonOptions { get; } = new() { PropertyNameCaseInsensitive = true, PropertyNamingPolicy = JsonNamingPolicy.CamelCase, WriteIndented = true, }; public static RouteGraph LoadFile(string path) { string json = File.ReadAllText(path); RouteGraph graph = JsonSerializer.Deserialize(json, JsonOptions) ?? throw new InvalidOperationException($"无法解析路由图:{path}"); graph.Validate(); return graph; } public void Validate() { if (Nodes.Count == 0) { throw new InvalidOperationException("图里没有任何节点。方案 C 至少要放一个专家到白名单。"); } HashSet ids = new(StringComparer.OrdinalIgnoreCase); foreach (RouteNode node in Nodes) { if (string.IsNullOrWhiteSpace(node.Id)) { throw new InvalidOperationException("节点缺少 id。"); } if (!ids.Add(node.Id)) { throw new InvalidOperationException($"节点 id 重复:{node.Id}"); } if (!SystemNodeTypes.IsKnown(node.Type)) { throw new InvalidOperationException( $"节点 `{node.Id}` 的类型 `{node.Type}` 不是系统专家。当前支持 {SystemNodeTypes.FileCity} / {SystemNodeTypes.Weather}。"); } } foreach (RouteEdge edge in Edges) { if (Find(edge.From) is null) { throw new InvalidOperationException($"边的 from `{edge.From}` 不在图上。"); } if (Find(edge.To) is null) { throw new InvalidOperationException($"边的 to `{edge.To}` 不在图上。"); } } } } public sealed class RouteNode { public string Id { get; set; } = ""; public string Type { get; set; } = ""; public string Title { get; set; } = ""; public Dictionary Config { get; set; } = new(StringComparer.OrdinalIgnoreCase); } public sealed class RouteEdge { public string From { get; set; } = ""; public string To { get; set; } = ""; /// 可选。hasValidCities / !hasValidCities / 空=只要拓扑允许就可以走。 public string? When { get; set; } }