using System; using System.Collections.Generic; using ExperionCrawler.Core.Application.Feedforward; using ExperionCrawler.Infrastructure.Control; using Xunit; namespace ExperionCrawler.Tests; // 온도 HIGH LIMIT(raw, UI 설정) → 전환류 트리거(단독 severe). 코러보 불요. public class FeedforwardTempHighLimitTests { private static ColumnConfig Cfg(double tempHigh) => new() { Id = 1, Name = "C-TH", Enabled = true, FeedTag = "f", ProductKey = "P", ScanSec = 2, PressureTag = null, FeedMoveThresholdPerMin = 0.0, RecoveryEnabled = true, RecoveryAutoArm = true, // 자동무장(테스트 간소화) ImbalanceTriggerFrac = 0.10, ImbalanceTriggerSec = 4, // 짧게 TempHighLimit = tempHigh, 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 maxTemp) => 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", maxTemp, true, DateTime.UtcNow), new TagSample("b", maxTemp - 5, true, DateTime.UtcNow), new TagSample("c", maxTemp - 10, true, DateTime.UtcNow), new TagSample("d", maxTemp - 20, true, DateTime.UtcNow) } }; [Fact] public void Temp_above_high_limit_triggers_recovery_standalone() { var engine = new FeedforwardEngine(); var st = new ColumnState(); var cfg = Cfg(tempHigh: 120); // 한계 120 AdvisoryResult res = null!; string entryReason = ""; // 최고온도 130 > 120, 다른 신호(vloss/ΔP) 없음에도 지속 시 단독 트리거. // 진입 틱의 사유에 "온도HIGH" 표기(이후 틱은 평형대기). for (int i = 0; i < 5; i++) { res = engine.Tick(cfg, Snap(130), st, DateTime.UtcNow); if (res.Mode == ColumnMode.Recovering && (res.ModeReason ?? "").Contains("진입")) entryReason = res.ModeReason!; } Assert.Equal(ColumnMode.Recovering, res.Mode); Assert.Contains("온도HIGH", entryReason); } [Fact] public void Temp_below_limit_no_trigger() { var engine = new FeedforwardEngine(); var st = new ColumnState(); var cfg = Cfg(tempHigh: 120); AdvisoryResult res = null!; for (int i = 0; i < 6; i++) res = engine.Tick(cfg, Snap(110), st, DateTime.UtcNow); // 110 < 120 Assert.Equal(ColumnMode.Normal, res.Mode); } [Fact] public void Default_limit_disabled() { var engine = new FeedforwardEngine(); var st = new ColumnState(); var cfg = Cfg(tempHigh: double.MaxValue); // 기본=비활성 AdvisoryResult res = null!; for (int i = 0; i < 6; i++) res = engine.Tick(cfg, Snap(500), st, DateTime.UtcNow); // 매우 높아도 Assert.Equal(ColumnMode.Normal, res.Mode); } }