Files
ExperionCrawler/tests/ExperionCrawler.Tests/FeedforwardFront2PointTests.cs
windpacer dae8d7f902 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>
2026-06-01 20:27:52 +09:00

64 lines
2.9 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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(CD), 하부=ΔT(CB).
// 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) → CB 증가 → 하부 "상승". 상부(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) → CD 감소 → 상부 "하강". 하부(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); // CD = 10090
Assert.Equal(-5.0, res.LowerFrontMetric!.Value, 6); // CB = 100105
}
}