- FeedforwardConfigStore: advisory_only를 GetBoolean(31)로 읽어 IndexOutOfRange (컬럼 31개=ordinal 0~30, advisory_only=30). 30으로 수정 → FF supervisor 루프 복구 - FeedforwardEngine.ApplyFront: front metric을 Delta(temps[0],temps[^1])=하단−상단으로 계산해 부호 반전(프론트 상승 시 trim 권고 역전). Delta(temps[^1],temps[0])=상단−하단으로 수정 - FeedforwardFrontTests: 엔진 경유 부호 회귀 테스트 2건 추가 (24/24 통과) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
91 lines
4.1 KiB
C#
91 lines
4.1 KiB
C#
using ExperionCrawler.Core.Application.Feedforward;
|
|
using ExperionCrawler.Infrastructure.Control;
|
|
using Xunit;
|
|
|
|
namespace ExperionCrawler.Tests;
|
|
|
|
public class FeedforwardFrontTests
|
|
{
|
|
[Fact]
|
|
public void Front_stable_within_band()
|
|
{
|
|
var ind = new FrontPositionIndicator(bandwidth: 0.3);
|
|
for (int i = 0; i < 50; i++) ind.Update(100.0, tsSec: 2, refTauSec: 60, strongSignal: true);
|
|
var (state, trim, grade) = ind.Update(100.1, 2, 60, true);
|
|
Assert.Contains("정상", state);
|
|
Assert.Null(trim);
|
|
Assert.Equal(Confidence.B, grade);
|
|
}
|
|
|
|
[Fact]
|
|
public void Front_rise_triggers_reflux_advice()
|
|
{
|
|
var ind = new FrontPositionIndicator(bandwidth: 0.3);
|
|
for (int i = 0; i < 200; i++) ind.Update(100.0, tsSec: 2, refTauSec: 60, strongSignal: false);
|
|
var (state, trim, grade) = ind.Update(105.0, 2, 60, false);
|
|
Assert.Contains("상승", state);
|
|
Assert.Equal("환류↑ 권장", trim);
|
|
Assert.Equal(Confidence.C, grade);
|
|
}
|
|
|
|
[Fact]
|
|
public void Front_fall_triggers_boilup_advice()
|
|
{
|
|
var ind = new FrontPositionIndicator(bandwidth: 0.3);
|
|
for (int i = 0; i < 200; i++) ind.Update(100.0, tsSec: 2, refTauSec: 60, strongSignal: true);
|
|
var (state, trim, _) = ind.Update(95.0, 2, 60, true);
|
|
Assert.Contains("하강", state);
|
|
Assert.Contains("boilup", trim);
|
|
}
|
|
|
|
// ── ApplyFront 부호 규약 (브레인스토밍 §10.2-A 버그픽스 회귀) ──────────
|
|
// temp_tags = [A하단 … D상단], 정상 A>B>C>D 단조감소.
|
|
// front metric은 "상단−하단"이어야 함: 프론트 상승(heavies↑→상단 가열) 시 metric↑ → dev>0 → 상승/환류↑.
|
|
private static ColumnConfig FrontCfg() => new()
|
|
{
|
|
Id = 1, Name = "C-FRONT", Enabled = true, FeedTag = "f", ProductKey = "P", ScanSec = 2,
|
|
DTdP = 0.0, PRef = double.NaN, PressureTag = null, // 압력경로 비활성 → pUnstable 없음
|
|
FeedMoveThresholdPerMin = 0.0, // moving 비활성 → transient 없음
|
|
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 FrontSnap(double a, double b, double c, double d) => new(
|
|
new TagSample("f", 100, true, DateTime.UtcNow),
|
|
null,
|
|
Array.Empty<TagSample>(),
|
|
new Dictionary<string, TagSample> { ["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 ApplyFront_top_warming_is_front_rise_reflux_advice()
|
|
{
|
|
var engine = new FeedforwardEngine();
|
|
var st = new ColumnState();
|
|
// 정상 단조감소(A=110>B>C>D=90)로 baseline 시드
|
|
for (int i = 0; i < 5; i++) engine.Tick(FrontCfg(), FrontSnap(110, 105, 100, 90), st, DateTime.UtcNow);
|
|
// 상단(D) 가열 = heavies 상승 = 프론트 상승 → 상승/환류↑ (버그였다면 하강/boilup로 반전)
|
|
var res = engine.Tick(FrontCfg(), FrontSnap(110, 105, 100, 100), st, DateTime.UtcNow);
|
|
Assert.Contains("상승", res.FrontPositionState);
|
|
Assert.Equal("환류↑ 권장", res.FrontTrimAdvice);
|
|
}
|
|
|
|
[Fact]
|
|
public void ApplyFront_top_cooling_is_front_fall_boilup_advice()
|
|
{
|
|
var engine = new FeedforwardEngine();
|
|
var st = new ColumnState();
|
|
for (int i = 0; i < 5; i++) engine.Tick(FrontCfg(), FrontSnap(110, 105, 100, 90), st, DateTime.UtcNow);
|
|
// 상단(D) 추가 냉각 = 프론트 하강 → 하강/boilup
|
|
var res = engine.Tick(FrontCfg(), FrontSnap(110, 105, 100, 80), st, DateTime.UtcNow);
|
|
Assert.Contains("하강", res.FrontPositionState);
|
|
Assert.Contains("boilup", res.FrontTrimAdvice);
|
|
}
|
|
}
|