Phase II:
- FfOperatorAction entity + ff_operator_action DDL/DbSet
- IFeedforwardWriteGuard + FeedforwardWriteGuard (SP bounds, grade C, transient, NaN)
- IFeedforwardAuditService + FeedforwardAuditService (raw ADO insert/query)
- FeedforwardSupervisor.AutoWriteAsync (per-stream OPC UA after Tick, rate-limited)
- FeedforwardConfigStore: advisory_only now read/writes DB, sp_node_id column
- FeedforwardController: auth (X-Kb-Token) on config/delete/write/audit;
POST write/{id}/{key} manual SP write; GET audit; write results in MapColumn
- ff.js: token header, auto-write badge, per-stream write result, spNodeId, advisoryOnly
- ff.css: .ff-write-badge, .ff-write, .ff-write-err, .ff-wg-blocked
- Program.cs: register audit (Scoped) + write guard (Singleton)
WO-2~7 (build 0W/0E, test 22/22):
- PCT monitor, θ auto-tune, slow bias, front position indicator,
total reflux recovery, config form expansion
41 lines
1.4 KiB
C#
41 lines
1.4 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);
|
|
}
|
|
}
|