using System; using System.Collections.Generic; using System.Linq; using ExperionCrawler.Core.Application.Feedforward; using ExperionCrawler.Infrastructure.Control; using Xunit; namespace ExperionCrawler.Tests; public class FeedforwardBiasTests { private static ColumnConfig Cfg() => new() { Id = 1, Name = "C-BIAS", Enabled = true, FeedTag = "f", ProductKey = "P", ScanSec = 2, BiasMaWindowSec = 20, // 10 샘플 창 Streams = new[] { new StreamConfig { Key = "P", FlowTag = "p", Role = StreamRole.Commanded, Grade = Confidence.A, TargetCoeff = 0.95 }, new StreamConfig { Key = "D", FlowTag = "d", Role = StreamRole.LevelDriven, Grade = Confidence.B, TargetCoeff = 0.02 }, new StreamConfig { Key = "B", FlowTag = "b", Role = StreamRole.LevelDriven, Grade = Confidence.B, TargetCoeff = 0.03 }, } }; // FEED 100 고정, P=95 → K_obs ≈ 0.95, D/B는 물질수지 충족용 private static PvSnapshot Snap() => new( new TagSample("f", 100, true, DateTime.UtcNow), null, Array.Empty(), new Dictionary { ["P"] = new("p", 95, true, DateTime.UtcNow), ["D"] = new("d", 2, true, DateTime.UtcNow), ["B"] = new("b", 3, true, DateTime.UtcNow), }); [Fact] public void KObs_and_VLossMa_accumulate_in_steady_state() { var engine = new FeedforwardEngine(); var st = new ColumnState(); AdvisoryResult res = engine.Tick(Cfg(), Snap(), st, DateTime.UtcNow); for (int i = 0; i < 20; i++) res = engine.Tick(Cfg(), Snap(), st, DateTime.UtcNow); var p = res.Streams.First(s => s.Key == "P"); Assert.NotNull(p.KObsSuggest); Assert.InRange(p.KObsSuggest!.Value, 0.94, 0.96); // 95/100 Assert.NotNull(res.VLossMa); Assert.InRange(res.VLossMa!.Value, -0.5, 0.5); // 100-(95+2+3)=0 } }