Files
ExperionCrawler/src/Infrastructure/Control/FeedforwardWriteGuard.cs
windpacer 7c26aa7361 feat: Phase II auto-write (WriteGuard, audit, auth) + WO-2~7 완료
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
2026-05-31 20:30:06 +09:00

36 lines
1.3 KiB
C#

using ExperionCrawler.Core.Application.Feedforward;
namespace ExperionCrawler.Infrastructure.Control;
public sealed class FeedforwardWriteGuard : IFeedforwardWriteGuard
{
public WriteCheckResult Check(ColumnConfig cfg, StreamAdvisory adv, StreamConfig sc, AdvisoryResult column)
{
if (adv.Role != StreamRole.Commanded)
return new WriteCheckResult(false, "Commanded 스트림만 SP 쓰기 대상");
if (cfg.AdvisoryOnly)
return new WriteCheckResult(false, "컬럼이 AdvisoryOnly 모드");
if (adv.RecommendedSp is not double sp)
return new WriteCheckResult(false, "권장 SP 없음");
if (!adv.Valid)
return new WriteCheckResult(false, "Advisory 무효(전환류 오버라이드 등)");
if (adv.Grade == Confidence.C)
return new WriteCheckResult(false, $"신뢰도 C — SP 쓰기 차단");
if (column.Transient)
return new WriteCheckResult(false, "과도 상태 — SP 쓰기 차단");
if (sp < sc.SpMin || sp > sc.SpMax)
return new WriteCheckResult(false, $"SP {sp:F2} 허용범위 [{sc.SpMin:F2}, {sc.SpMax:F2}] 벗어남");
if (double.IsNaN(sp) || double.IsInfinity(sp))
return new WriteCheckResult(false, "SP 값이 NaN/Infinity");
return new WriteCheckResult(true, null);
}
}