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.6 KiB
C#
41 lines
1.6 KiB
C#
using System;
|
|
using ExperionCrawler.Infrastructure.Control;
|
|
using Xunit;
|
|
|
|
namespace ExperionCrawler.Tests;
|
|
|
|
public class FeedforwardThetaTests
|
|
{
|
|
// 알려진 지연(5 샘플)으로 응답이 피드를 따라가면 θ≈5*ts 로 식별되어야 함
|
|
[Fact]
|
|
public void Estimator_finds_known_lag()
|
|
{
|
|
var est = new CrossCorrLagEstimator(maxLagSamples: 20, historySamples: 400,
|
|
minSignalStd: 1e-9, recomputeEvery: 1);
|
|
var feed = new System.Collections.Generic.List<double>();
|
|
(double thetaUpSec, double thetaDnSec, double conf)? last = null;
|
|
for (int t = 0; t < 400; t++)
|
|
{
|
|
double df = Math.Sin(t * 0.3); // 풍부한 양/음 외란
|
|
feed.Add(df);
|
|
double dr = t >= 5 ? feed[t - 5] : 0.0; // 응답 = 피드 5샘플 지연
|
|
last = est.Push(df, dr, 0.0, tsSec: 1.0); // 스팀 0
|
|
}
|
|
Assert.NotNull(last);
|
|
Assert.InRange(last!.Value.thetaUpSec, 4.0, 6.0);
|
|
Assert.InRange(last!.Value.thetaDnSec, 4.0, 6.0);
|
|
Assert.True(last!.Value.conf > 0.5);
|
|
}
|
|
|
|
// 피드 외란이 없으면(평탄) 제안 억제(null)
|
|
[Fact]
|
|
public void Estimator_suppresses_when_no_excitation()
|
|
{
|
|
var est = new CrossCorrLagEstimator(maxLagSamples: 20, historySamples: 400,
|
|
minSignalStd: 1e-6, recomputeEvery: 1);
|
|
(double, double, double)? last = (0, 0, 0);
|
|
for (int t = 0; t < 200; t++) last = est.Push(0.0, 0.0, 0.0, 1.0); // Δ 전부 0
|
|
Assert.Null(last);
|
|
}
|
|
}
|