using System.Text.Json;
using CommunityToolkit.Mvvm.ComponentModel;
using XFEEDToolBox.Core.Execution;
using XFEEDToolBox.Core.Models;
using XFEEDToolBox.Core.Planning;
namespace XFEEDToolBox.ViewModels;
public partial class ToolboxViewModel
{
[ObservableProperty]public partial string AutomaticSupplySummary{get;set;}="";
private async Task<AutomaticSupplyChoice?> SelectAutomaticSupplyAsync(AutomaticSupplyRequest value,CancellationToken token)
{
var config=value.Configuration;var player=value.Player;var settings=runtime.Settings;
if(value.Credits<=value.Reserve)return null;
if(player.MaxJumpRange is not (>0 and <=500))throw new InvalidOperationException("正在等待当前飞船的实际跳跃射程,未使用假定船型参数。");
var pad=ShipPadPolicy.RequiredPad(player.Ship);
var saved=config.SourcePlanning??settings.Planner;
var center=config.SourceCenterFollowsPlayer?player.Position:saved.SearchCenterPosition??settings.MarketCacheCenterPosition;
var request=saved with{StartMarketId=config.Destination.MarketId,Credits=value.Credits,ReserveCredits=value.Reserve,
InvestmentCredits=config.InvestmentCredits,CargoCapacity=player.CargoCapacity,OccupiedCargo=player.CargoCount,
ActualShipType=player.Ship,ShipRequiredPad=pad,RequiredPad=(LandingPad)Math.Max((int)pad,(int)saved.RequiredPad),
CurrentPosition=player.Position,CurrentSystemName=player.SystemName,CurrentMarketId=player.MarketId,
LadenJumpRangeLy=player.MaxJumpRange.Value,JumpRangeIsEstimate=true,SearchCenterPosition=center,
SearchCenterName=config.SourceCenterFollowsPlayer?player.SystemName:saved.SearchCenterName,
SearchRadiusLy=config.SourcePlanning?.SearchRadiusLy??settings.MarketCacheRadiusLy,AsOf=DateTimeOffset.UtcNow};
var membershipPath=Path.Combine(config.WorkingDirectory,"membership","membership-latest.json");
var membership=JsonSerializer.Deserialize<OriginalCommunityGoalRead>(await File.ReadAllTextAsync(membershipPath,token),ToolboxSettings.JsonOptions)
??throw new InvalidDataException("CG报名与接收商品尚未就绪。");
var goal=CommunityGoalCatalogue.RequireDestinationTrade(membership.Goals,config.Destination.MarketId,config.CommunityGoalId,request.AsOf,true);
var accepted=config.Commodities.Intersect(goal.AcceptedCommodities,StringComparer.Ordinal).ToArray();
if(accepted.Length==0)throw new InvalidDataException("所选商品不在当前有效CG的实际接收范围内。");
var ready=new TaskCompletionSource<IProgress<TradePlanningProgress>>(TaskCreationOptions.RunContinuationsAsynchronously);
using var cancellation=token.Register(()=>ready.TrySetCanceled(token));
if(!dispatcher.TryEnqueue(()=>{if(!token.IsCancellationRequested)ready.TrySetResult(BeginTradePlanningProgress(token));}))
throw new OperationCanceledException("规划界面已关闭。",token);
var progress=await ready.Task.ConfigureAwait(false);
try
{
return await Task.Run(async()=>
{
var cached=runtime.Markets.LoadPlanningRegion(request,token:token,progress:progress);
var stations=cached.Stations.Append(config.Source).Append(config.Destination).DistinctBy(s=>s.MarketId)
.Where(s=>s.MarketId==config.Destination.MarketId||!value.ExcludedSourceMarkets.Contains(s.MarketId)).ToArray();
// A live station snapshot replaces its cached rows completely,
// including zero stock and commodities no longer offered.
var remaining=AutomaticSupplyPlanner.DeductConfirmedTrades(cached,value.ConfirmedTrades);
var merged=AutomaticSupplyPlanner.MergeObservations(remaining with{Stations=stations},value.ObservedQuotes);
var choices=AutomaticSupplyPlanner.Calculate(merged,request,config.Destination,accepted,token:token,progress:progress);
var proof=new{at=request.AsOf,goalId=goal.Id,request,stations=cached.Stations.Count,quotes=merged.Quotes.Count,
supplyBasis="缓存供需估算;实际到站报价优先,采购前及每轮交付后重算",choices};
var path=Path.Combine(config.WorkingDirectory,"supply-candidates-latest.json");
await File.WriteAllTextAsync(path+".tmp",JsonSerializer.Serialize(proof,ToolboxSettings.JsonOptions),token);
File.Move(path+".tmp",path,true);
return choices.FirstOrDefault();
},token).ConfigureAwait(false);
}
finally{dispatcher.TryEnqueue(EndTradePlanningProgress);}
}
}
using System.Text.Json;
using CommunityToolkit.Mvvm.ComponentModel;
using XFEEDToolBox.Core.Execution;
using XFEEDToolBox.Core.Models;
using XFEEDToolBox.Core.Planning;
namespace XFEEDToolBox.ViewModels;
public partial class ToolboxViewModel
{
[ObservableProperty]public partial string AutomaticSupplySummary{get;set;}="";
private async Task<AutomaticSupplyChoice?> SelectAutomaticSupplyAsync(AutomaticSupplyRequest value,CancellationToken token)
{
var config=value.Configuration;var player=value.Player;var settings=runtime.Settings;
if(value.Credits<=value.Reserve)return null;
if(player.MaxJumpRange is not (>0 and <=500))throw new InvalidOperationException("正在等待当前飞船的实际跳跃射程,未使用假定船型参数。");
var pad=ShipPadPolicy.RequiredPad(player.Ship);
var saved=config.SourcePlanning??settings.Planner;
var center=config.SourceCenterFollowsPlayer?player.Position:saved.SearchCenterPosition??settings.MarketCacheCenterPosition;
var request=saved with{StartMarketId=config.Destination.MarketId,Credits=value.Credits,ReserveCredits=value.Reserve,
InvestmentCredits=config.InvestmentCredits,CargoCapacity=player.CargoCapacity,OccupiedCargo=player.CargoCount,
ActualShipType=player.Ship,ShipRequiredPad=pad,RequiredPad=(LandingPad)Math.Max((int)pad,(int)saved.RequiredPad),
CurrentPosition=player.Position,CurrentSystemName=player.SystemName,CurrentMarketId=player.MarketId,
LadenJumpRangeLy=player.MaxJumpRange.Value,JumpRangeIsEstimate=true,SearchCenterPosition=center,
SearchCenterName=config.SourceCenterFollowsPlayer?player.SystemName:saved.SearchCenterName,
SearchRadiusLy=config.SourcePlanning?.SearchRadiusLy??settings.MarketCacheRadiusLy,AsOf=DateTimeOffset.UtcNow};
var membershipPath=Path.Combine(config.WorkingDirectory,"membership","membership-latest.json");
var membership=JsonSerializer.Deserialize<OriginalCommunityGoalRead>(await File.ReadAllTextAsync(membershipPath,token),ToolboxSettings.JsonOptions)
??throw new InvalidDataException("CG报名与接收商品尚未就绪。");
var goal=CommunityGoalCatalogue.RequireDestinationTrade(membership.Goals,config.Destination.MarketId,config.CommunityGoalId,request.AsOf,true);
var accepted=config.Commodities.Intersect(goal.AcceptedCommodities,StringComparer.Ordinal).ToArray();
if(accepted.Length==0)throw new InvalidDataException("所选商品不在当前有效CG的实际接收范围内。");
var ready=new TaskCompletionSource<IProgress<TradePlanningProgress>>(TaskCreationOptions.RunContinuationsAsynchronously);
using var cancellation=token.Register(()=>ready.TrySetCanceled(token));
if(!dispatcher.TryEnqueue(()=>{if(!token.IsCancellationRequested)ready.TrySetResult(BeginTradePlanningProgress(token));}))
throw new OperationCanceledException("规划界面已关闭。",token);
var progress=await ready.Task.ConfigureAwait(false);
try
{
return await Task.Run(async()=>
{
var cached=runtime.Markets.LoadPlanningRegion(request,token:token,progress:progress);
var stations=cached.Stations.Append(config.Source).Append(config.Destination).DistinctBy(s=>s.MarketId)
.Where(s=>s.MarketId==config.Destination.MarketId||!value.ExcludedSourceMarkets.Contains(s.MarketId)).ToArray();
// A live station snapshot replaces its cached rows completely,
// including zero stock and commodities no longer offered.
var remaining=AutomaticSupplyPlanner.DeductConfirmedTrades(cached,value.ConfirmedTrades);
var merged=AutomaticSupplyPlanner.MergeObservations(remaining with{Stations=stations},value.ObservedQuotes);
var choices=AutomaticSupplyPlanner.Calculate(merged,request,config.Destination,accepted,token:token,progress:progress);
var proof=new{at=request.AsOf,goalId=goal.Id,request,stations=cached.Stations.Count,quotes=merged.Quotes.Count,
supplyBasis="缓存供需估算;实际到站报价优先,采购前及每轮交付后重算",choices};
var path=Path.Combine(config.WorkingDirectory,"supply-candidates-latest.json");
await File.WriteAllTextAsync(path+".tmp",JsonSerializer.Serialize(proof,ToolboxSettings.JsonOptions),token);
File.Move(path+".tmp",path,true);
return choices.FirstOrDefault();
},token).ConfigureAwait(false);
}
finally{dispatcher.TryEnqueue(EndTradePlanningProgress);}
}
}