feat: WP5 1단계 — 2-point front (상부 C-D / 하부 C-B)
- ColumnState UpperFrontBase/LowerFrontBase, 엔진 ApplyFront2Point(C=pivot temps[n-2], 느린 baseline 편차→중립 상태 정상/상승/하강) - AdvisoryResult Upper/LowerFrontState·Metric + 컨트롤러 노출 + ff.js/css 표시 - 단위 3건(상/하 격리 perturb, metric 노출). 45/45. 2단계(trim)·3단계(조성base) 미구현 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
63
tests/ExperionCrawler.Tests/FeedforwardFront2PointTests.cs
Normal file
63
tests/ExperionCrawler.Tests/FeedforwardFront2PointTests.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
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<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 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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user