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