feat: Sim Override를 FF 엔진까지 확장 (S7/§10/front 자율검증)
- FeedforwardSupervisor.BuildSnapshotAsync Sample/SampleExact: override 우선(신선) → /api/ff/advisory(엔진)도 override 반영 - 안전가드: _sim.Enabled 시 auto-write 억제(가짜 입력→실제 OPC 쓰기 방지) - 해소: S7(mbState)·§10/front 자율검증 가능. 잔여: S6(override=fresh)·P4(FeedMoveThresholdPerMin=0) - 작업지시서 WP0 한계 갱신 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -111,12 +111,11 @@ pica-6111 탑정 / REFLUX DIST(R) / TI-6111D / 상부PACKING(정류,D) /
|
|||||||
- `SimOverrideEnabled=false`(기본)에서 sim 엔드포인트 403, advisor는 live만 사용.
|
- `SimOverrideEnabled=false`(기본)에서 sim 엔드포인트 403, advisor는 live만 사용.
|
||||||
- 코드 grep: sim 경로에서 `WriteTagAsync`/SQL write **0건**.
|
- 코드 grep: sim 경로에서 `WriteTagAsync`/SQL write **0건**.
|
||||||
|
|
||||||
### ⚠ 한계 (구현 후 확인, 2026-06-01)
|
### 한계 → 엔진 확장으로 일부 해소 (2026-06-01)
|
||||||
**Sim Override는 `FeedRampAdvisorService`(ramp-advisor)만 통합. 엔진(`FeedforwardSupervisor.BuildSnapshotAsync`)은 DB를 직접 읽어 override 미반영.**
|
초기: Sim Override가 `FeedRampAdvisorService`(ramp-advisor)만 통합, 엔진 미반영.
|
||||||
- 결과: **S7(mbState 과추출)·S6(stale) 라이브 검증은 override로 불가** — `/api/ff/advisory`(엔진 산출)는 override 무시.
|
**확장 적용**: `FeedforwardSupervisor.BuildSnapshotAsync`의 `Sample`/`SampleExact`가 override 우선 → `/api/ff/advisory`(엔진 산출)도 override 반영. **안전가드**: `_sim.Enabled` 시 auto-write 억제(가짜 입력이 실제 OPC 쓰기 유발 방지).
|
||||||
- S7 라이브 = 운전원이 실제 DCS/realtime_table에 합성값 주입 / 또는 Engine 통합테스트. S6 라이브 = stale 태그(운전원) / 또는 단위테스트(`pv.Feed.Good=false`, 구현됨).
|
- **해소**: S7(mbState 과추출)·§10/front 물리검증은 이제 **override로 자율 가능**(B·temps 주입 → 다음 supervisor tick~2s 반영).
|
||||||
- 라이브 override로 검증 가능한 건 **S1~S5(ramp-advisor 경로)뿐** — 전부 통과 확인(2026-06-01).
|
- **여전히 불가**: S6(stale) — override는 항상 Good=fresh라 stale 시뮬 불가(단위테스트 `pv.Feed.Good=false`로 커버). P4(transient) — `FeedMoveThresholdPerMin=0`이라 config 임계 설정 필요(override만으론 미발화).
|
||||||
- 후속 옵션: 엔진 스냅샷 빌드도 `ISimOverrideStore` 경유하도록 확장하면 S6·S7도 override로 자율 검증 가능(현재 미적용).
|
|
||||||
|
|
||||||
### 실행자 사용 예 (자율 루프)
|
### 실행자 사용 예 (자율 루프)
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ public sealed class FeedforwardSupervisor : BackgroundService
|
|||||||
private readonly IFeedforwardWriteGuard _writeGuard;
|
private readonly IFeedforwardWriteGuard _writeGuard;
|
||||||
private readonly ILogger<FeedforwardSupervisor> _logger;
|
private readonly ILogger<FeedforwardSupervisor> _logger;
|
||||||
private readonly Microsoft.Extensions.Configuration.IConfiguration _appConfig;
|
private readonly Microsoft.Extensions.Configuration.IConfiguration _appConfig;
|
||||||
|
private readonly ISimOverrideStore _sim; // WP0 확장: 엔진 스냅샷 입력 치환(DEMO)
|
||||||
private readonly Dictionary<int, ColumnState> _states = new();
|
private readonly Dictionary<int, ColumnState> _states = new();
|
||||||
// Phase II: 마지막 쓰기 시각(스트림별 rate-limit) 및 결과
|
// Phase II: 마지막 쓰기 시각(스트림별 rate-limit) 및 결과
|
||||||
private readonly ConcurrentDictionary<(int colId, string streamKey), DateTime> _lastWriteTimes = new();
|
private readonly ConcurrentDictionary<(int colId, string streamKey), DateTime> _lastWriteTimes = new();
|
||||||
@@ -26,8 +27,9 @@ public sealed class FeedforwardSupervisor : BackgroundService
|
|||||||
IServiceScopeFactory scopeFactory, FeedforwardEngine engine,
|
IServiceScopeFactory scopeFactory, FeedforwardEngine engine,
|
||||||
IFeedforwardAdvisoryStore store, IFeedforwardWriteGuard writeGuard,
|
IFeedforwardAdvisoryStore store, IFeedforwardWriteGuard writeGuard,
|
||||||
ILogger<FeedforwardSupervisor> logger,
|
ILogger<FeedforwardSupervisor> logger,
|
||||||
Microsoft.Extensions.Configuration.IConfiguration appConfig)
|
Microsoft.Extensions.Configuration.IConfiguration appConfig,
|
||||||
{ _scopeFactory = scopeFactory; _engine = engine; _store = store; _writeGuard = writeGuard; _logger = logger; _appConfig = appConfig; }
|
ISimOverrideStore sim)
|
||||||
|
{ _scopeFactory = scopeFactory; _engine = engine; _store = store; _writeGuard = writeGuard; _logger = logger; _appConfig = appConfig; _sim = sim; }
|
||||||
|
|
||||||
// Phase II: 쓰기 결과 조회 (Controller에서 사용)
|
// Phase II: 쓰기 결과 조회 (Controller에서 사용)
|
||||||
public (double? sp, string? error, DateTime? at) GetLastWrite(int colId, string streamKey)
|
public (double? sp, string? error, DateTime? at) GetLastWrite(int colId, string streamKey)
|
||||||
@@ -59,11 +61,14 @@ public sealed class FeedforwardSupervisor : BackgroundService
|
|||||||
var st = GetState(cfg.Id);
|
var st = GetState(cfg.Id);
|
||||||
var res = _engine.Tick(cfg, snap, st, DateTime.UtcNow);
|
var res = _engine.Tick(cfg, snap, st, DateTime.UtcNow);
|
||||||
// Phase II: auto-write
|
// Phase II: auto-write
|
||||||
if (!cfg.AdvisoryOnly && writeClient is not null && auditService is not null)
|
// 안전가드: Sim Override 활성 시 입력이 가짜이므로 실제 쓰기 금지(advisory-only로 강등)
|
||||||
|
if (!cfg.AdvisoryOnly && writeClient is not null && auditService is not null && !_sim.Enabled)
|
||||||
{
|
{
|
||||||
await AutoWriteAsync(cfg, res, st, writeClient, auditService, ct);
|
await AutoWriteAsync(cfg, res, st, writeClient, auditService, ct);
|
||||||
res = res with { AutoWriteActive = true };
|
res = res with { AutoWriteActive = true };
|
||||||
}
|
}
|
||||||
|
else if (!cfg.AdvisoryOnly && _sim.Enabled)
|
||||||
|
_logger.LogWarning("[FF] Sim Override 활성 — col {Id} auto-write 억제(가짜 입력)", cfg.Id);
|
||||||
_store.Set(res);
|
_store.Set(res);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -184,6 +189,8 @@ public sealed class FeedforwardSupervisor : BackgroundService
|
|||||||
TagSample Sample(string baseTag)
|
TagSample Sample(string baseTag)
|
||||||
{
|
{
|
||||||
var tag = PvTag(baseTag);
|
var tag = PvTag(baseTag);
|
||||||
|
if (_sim.Enabled && _sim.TryGet(tag, out var sov)) // WP0 확장: override 우선(신선 처리)
|
||||||
|
return new TagSample(tag, sov, Good: true, DateTime.UtcNow);
|
||||||
if (rows.TryGetValue(tag.ToLowerInvariant(), out var r)
|
if (rows.TryGetValue(tag.ToLowerInvariant(), out var r)
|
||||||
&& double.TryParse(r.LiveValue, NumberStyles.Float, CultureInfo.InvariantCulture, out var v))
|
&& double.TryParse(r.LiveValue, NumberStyles.Float, CultureInfo.InvariantCulture, out var v))
|
||||||
{
|
{
|
||||||
@@ -197,6 +204,8 @@ public sealed class FeedforwardSupervisor : BackgroundService
|
|||||||
TagSample SampleExact(string rawTag)
|
TagSample SampleExact(string rawTag)
|
||||||
{
|
{
|
||||||
var tag = rawTag.ToLowerInvariant();
|
var tag = rawTag.ToLowerInvariant();
|
||||||
|
if (_sim.Enabled && _sim.TryGet(tag, out var sov)) // WP0 확장: override 우선
|
||||||
|
return new TagSample(tag, sov, Good: true, DateTime.UtcNow);
|
||||||
if (rows.TryGetValue(tag, out var r)
|
if (rows.TryGetValue(tag, out var r)
|
||||||
&& double.TryParse(r.LiveValue, NumberStyles.Float, CultureInfo.InvariantCulture, out var v))
|
&& double.TryParse(r.LiveValue, NumberStyles.Float, CultureInfo.InvariantCulture, out var v))
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user