using System; using System.Collections.Generic; using ExperionCrawler.Core.Application.Feedforward; using ExperionCrawler.Infrastructure.Control; using Xunit; namespace ExperionCrawler.Tests; // WP5 1단계: 2-point front (C=제품 pivot=temps[n-2]). 상부=ΔT(C−D), 하부=ΔT(C−B). // temp_tags 하단→상단 [A,B,C,D]. dtdp=0 → pct=raw. 한 섹션만 perturb 시 그 front만 반응. public class FeedforwardFront2PointTests { private static ColumnConfig Cfg() => new() { Id = 1, Name = "C-F2", Enabled = true, FeedTag = "f", ProductKey = "P", ScanSec = 2, DTdP = 0.0, PRef = double.NaN, PressureTag = null, FeedMoveThresholdPerMin = 0.0, TempTags = new[] { "a", "b", "c", "d" }, Streams = new[] { new StreamConfig { Key = "P", FlowTag = "ficq-6118", Role = StreamRole.Commanded, Grade = Confidence.A, TargetCoeff = 0.95 } } }; private static PvSnapshot Snap(double a, double b, double c, double d) => new( new TagSample("f", 100, true, DateTime.UtcNow), null, Array.Empty(), new Dictionary { ["P"] = new TagSample("ficq-6118", 95, true, DateTime.UtcNow) }) { Temps = new[] { new TagSample("a", a, true, DateTime.UtcNow), new TagSample("b", b, true, DateTime.UtcNow), new TagSample("c", c, true, DateTime.UtcNow), new TagSample("d", d, true, DateTime.UtcNow) } }; [Fact] public void Lower_front_isolates_from_upper() { var engine = new FeedforwardEngine(); var st = new ColumnState(); // 정상 단조 A110>B105>C100>D90 으로 baseline 시드 for (int i = 0; i < 5; i++) engine.Tick(Cfg(), Snap(110, 105, 100, 90), st, DateTime.UtcNow); // 하부만 perturb: B 강하(105→95) → C−B 증가 → 하부 "상승". 상부(C,D 불변) "정상" var res = engine.Tick(Cfg(), Snap(110, 95, 100, 90), st, DateTime.UtcNow); Assert.Equal("상승", res.LowerFrontState); Assert.Equal("정상", res.UpperFrontState); } [Fact] public void Upper_front_isolates_from_lower() { var engine = new FeedforwardEngine(); var st = new ColumnState(); for (int i = 0; i < 5; i++) engine.Tick(Cfg(), Snap(110, 105, 100, 90), st, DateTime.UtcNow); // 상부만 perturb: D 가열(90→98) → C−D 감소 → 상부 "하강". 하부(C,B 불변) "정상" var res = engine.Tick(Cfg(), Snap(110, 105, 100, 98), st, DateTime.UtcNow); Assert.Equal("하강", res.UpperFrontState); Assert.Equal("정상", res.LowerFrontState); } [Fact] public void Metrics_exposed() { var engine = new FeedforwardEngine(); var st = new ColumnState(); var res = engine.Tick(Cfg(), Snap(110, 105, 100, 90), st, DateTime.UtcNow); Assert.Equal(10.0, res.UpperFrontMetric!.Value, 6); // C−D = 100−90 Assert.Equal(-5.0, res.LowerFrontMetric!.Value, 6); // C−B = 100−105 } }