using XFEEDToolBox.Core.Data; using XFEEDToolBox.Core.Execution; using XFEEDToolBox.Core.Models; namespace XFEEDToolBox.Core.Planning; /// Only original active trade initiatives, exact destination identities and confirmed goods admit a CG sale edge. public sealed class CommunityGoalSupplyScope { private readonly Dictionary> accepted; private CommunityGoalSupplyScope(IReadOnlyList goals,Dictionary> accepted,string? issue) {Goals=goals;this.accepted=accepted;Issue=issue;} public IReadOnlyList Goals{get;} public string? Issue{get;} public bool Available=>accepted.Values.Any(goods=>goods.Count>0); public static CommunityGoalSupplyScope Create(MarketBook book,IReadOnlyList? catalogue,DateTimeOffset now,IReadOnlyCollection? commodityFilter=null) { var confirmed=(catalogue??[]).GroupBy(goal=>goal.Id).Where(group=>group.Key is >0 and <=uint.MaxValue&&group.Count()==1).Select(group=>group.Single()) .Where(goal=>goal.Active&&goal.Expires>now&&goal.MarketId>0&&goal.GoalType=="trade"&&goal.Activity.StartsWith("trade",StringComparison.Ordinal) &&!string.IsNullOrWhiteSpace(goal.SystemName)&&!string.IsNullOrWhiteSpace(goal.StationName) &&goal.AcceptedCommodities.Count>0&&goal.AcceptedCommodities.All(goods=>!string.IsNullOrWhiteSpace(CommodityCatalog.Key(goods)))) .ToArray(); var byMarket=book.Stations.GroupBy(station=>station.MarketId).ToDictionary(group=>group.Key,group=>group.ToArray()); var matches=confirmed.Where(goal=>byMarket.TryGetValue(goal.MarketId,out var stations)&&stations.Length==1 &&stations[0].SystemName.Equals(goal.SystemName,StringComparison.OrdinalIgnoreCase)&&stations[0].Name.Equals(goal.StationName,StringComparison.OrdinalIgnoreCase)).ToArray(); var accepted=matches.GroupBy(goal=>goal.MarketId).ToDictionary(group=>group.Key,group=>group.SelectMany(goal=>goal.AcceptedCommodities) .Select(goods=>CommodityCatalog.Key(goods)).ToHashSet(StringComparer.Ordinal)); var filter=TradeSearchConstraints.NormalizeCommodityFilter(commodityFilter).ToHashSet(StringComparer.Ordinal); if(filter.Count>0)foreach(var goods in accepted.Values)goods.IntersectWith(filter); var issue=matches.Length>0?accepted.Values.Any(goods=>goods.Count>0)?null:"商品筛选与有效 CG 的接收商品没有交集;请清空商品筛选或选择活动接收的商品。未回退普通跑商。":confirmed.Length>0 ?"当前行情范围内没有身份匹配的 CG 交付站;请调整中心或半径并补全活动站行情。未回退普通跑商。" :"未确认仍有效的贸易 CG、交付站与接收商品,暂不能推荐 CG 供货路线。未回退普通跑商。"; return new(matches,accepted,issue); } public bool AllowsSale(CommodityQuote quote)=>accepted.TryGetValue(quote.MarketId,out var goods)&&goods.Contains(CommodityCatalog.Key(quote.Commodity)); public static bool MatchesRoute(IReadOnlyList catalogue,RoutePlan plan,DateTimeOffset now) { var stations=plan.Legs.SelectMany(leg=>new[]{leg.From,leg.To}).DistinctBy(station=>station.MarketId).ToArray(); var scope=Create(new(stations,[]),catalogue,now,plan.Request.CommunityGoalCommodityFilter); var deliveries=plan.Legs.Where(leg=>leg.Orders.Count>0).ToArray(); // Empty return legs are logistics. Every monetary delivery must serve // a currently valid CG; a single accepted item cannot bless unrelated cargo. return deliveries.Length>0&&deliveries.All(leg=>leg.Orders.All(order=>order.Quantity>0&&scope.accepted.TryGetValue(leg.To.MarketId,out var allowed) &&allowed.Contains(CommodityCatalog.Key(order.Commodity))&&scope.Goals.Any(goal=>goal.MarketId==leg.To.MarketId &&goal.SystemName.Equals(leg.To.SystemName,StringComparison.OrdinalIgnoreCase)&&goal.StationName.Equals(leg.To.Name,StringComparison.OrdinalIgnoreCase) &&goal.AcceptedCommodities.Select(goods=>CommodityCatalog.Key(goods)).Contains(CommodityCatalog.Key(order.Commodity),StringComparer.Ordinal)))); } }