Compare commits
3 Commits
54ca4d0d62
...
1811e1fed7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1811e1fed7 | ||
|
|
60946f3c47 | ||
|
|
9065b19a0a |
@@ -57,7 +57,7 @@ steamTo = fiq6115Cur × (clampedTarget/currentFeed) + sensibleGain × clamped
|
||||
| **S4** | flooding ceiling | pi-6111b=**120**(ΔPnow=80), **floodLimit=100, n=1.8**, targetFeed=1100, ΔI=50 | floodCeiling=900×(100/80)^(1/1.8)=**1018.5**, ceiling=min(2105,1018.5)=**1018.5**(binding=**flooding**), clampedTarget=1018.5, rampTimeMin=**36.1** | ceiling 1018.5±2% binding=flooding, clampedTarget 클램프 | 단위+sim |
|
||||
| **S5** | TI-6103 하강(현열 FF) | feed=900 고정, **ti-6103=140**, targetFeed=900, sensibleGain=0.001, feedTempRef=150 | steamTo=300×1 + 0.001×900×(150−140)=**309.0**(+9), rampTimeMin≈0 | steamTo 309±2%(피드 무변에도 steam↑), 현열항 부호 양수 | 단위+sim |
|
||||
| **S6** | stale/HOLD | feed 태그 Good=false(단위) / 운전원 frozen(>stale_sec=120s) | **HOLD** 반환 + warning="feed-bad/stale", 권장 미산정 | HOLD 상태·warning stale, 수치 미반환 | 단위(Good=false), 운전원 frozen |
|
||||
| **S7** | 과추출 감지(현행엔진) | B=**150**(vs 27), P=855, D=18 → D+P+B=1023 > feed → vloss=**−123** | (FeedRampCalculator 아님) `/api/ff/advisory` **massBalanceState="음의 손실(스팬 오류 의심)"** | mbState≠"정상"(음의손실/불일치) | **WP2 엔진**(/api/ff/advisory) |
|
||||
| **S7** | 과추출 감지(현행엔진) | B=**150**(vs 27), P=855, D=18 → D+P+B=1023 > feed → vloss≈**−120** | (FeedRampCalculator 아님) `/api/ff/advisory` **massBalanceState="물질수지 불일치(계측 점검)"** (\|vloss\|>3%·ff 조건이 먼저 걸림; 작은 음수만 "음의 손실") | mbState≠"정상" | **엔진**(/api/ff/advisory). override 엔진확장(2026-06-01) 후 **자율검증 가능**(라이브 −120.27 확인) |
|
||||
|
||||
---
|
||||
|
||||
|
||||
51
docs/안전피드램프-현행엔진-프로빙결과.md
Normal file
51
docs/안전피드램프-현행엔진-프로빙결과.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# WP2 — 현행 FF 엔진 프로빙 결과
|
||||
|
||||
> 2026-06-01. 대상: `plans/안전피드램프-advisory-작업지시서.md` WP2. 라이브 `:5000` `/api/ff/advisory` 관찰.
|
||||
> **방법 제약**: Sim Override는 엔진에 안 닿음(ramp-advisor만) → 본 프로빙은 **엔진이 현재 데모 합성데이터(설정값±variation)로 돌아가는 실제 거동을 관찰**. 의도적 step 주입이 필요한 항목(P4 등)은 운전원 DCS 주입 필요로 분리.
|
||||
|
||||
## P0 — 현재 advisory 스냅샷 (feed≈900.9, 정상상태)
|
||||
```
|
||||
transient=False mode=Normal yield=90.7%
|
||||
massBalanceState=물질수지 불일치 vLoss=43.3 vLossMa=31.7
|
||||
front=프론트 하강→boilup↑ (수 초 뒤 baseline 수렴하며 "정상(프론트 안정)"으로)
|
||||
temps(raw=pct): tica-6111a=79.21 ti-6111b=78.47 ti-6111c=78.69 ti-6111d=78.92
|
||||
streams: P rec=833.5(pv817) D rec=18.02(pv15.8) B rec=27.03(pv24.8) R rec=666.8(pv653.8)
|
||||
```
|
||||
|
||||
## 프로빙 결과 (코드리딩 주장 검증)
|
||||
|
||||
| 프로브 | 주장 | 관찰 | 판정 |
|
||||
|---|---|---|---|
|
||||
| **P1** | LevelDriven B/D rec = K×feed (clamp/rate 無) | B rec=**27.019 ≡ 0.03×900.63**, D rec=18.018≡0.02×feed. 시간추이서 feed와 **정확히 선형 추종**(27.018→27.021) | ✅ 확정 |
|
||||
| (대조) | Commanded P는 deadtime+lag+rate | P rec=**834.8 ≠ 0.95×feed(855.6)** — lag로 뒤처짐 | ✅ LevelDriven과 명확히 구분 |
|
||||
| **P2** | PCT 꺼짐(dtdp=0) | **모든 temp의 pct == raw**(79.2066=79.2066…) | ✅ PCT dormant 확정 |
|
||||
| **P3** | pi-6111b 미사용 | 엔진 태그목록(BuildSnapshotAsync)에 pi-6111b 없음 — 코드 사실(관찰 대상 아님) | ✅ 코드 확인 |
|
||||
| **P4** | feed 급변 시 transient/valid=false | **자연 variation(feed ±0.1)으론 transient 미발화**(FeedMoveThresholdPerMin=0이라 moving 조건 자체 off) | ⚠ 운전원 step 주입 필요 |
|
||||
| **P5** | D+P+B≠feed → mbState 불일치 | vLoss=43.3(>3%·feed=27) → "불일치", Bpv noise로 "정상"↔"불일치" 토글 | ✅ 단, **데모 PV 비보존 아티팩트**(실제 과추출 아님) |
|
||||
| **P6** | front 부호 수정 동작 | front는 tica-6111a(A)↔ti-6111d(D)로 산출, "하강↔안정" 변동. 부호픽스(76fdce8) 머지·단위검증 완료 | ⚠ 데모 temp 비물리라 **물리 방향 라이브 검증 불가** |
|
||||
|
||||
## ⚠ 핵심 발견 — 데모 temps가 물리 프로파일 위반
|
||||
운전원 확정 정상 프로파일은 **A>B>C>D 단조감소**(§10.1). 그러나 데모 실측은:
|
||||
```
|
||||
tica-6111a(A)=79.21 > ti-6111d(D)=78.92 > ti-6111c(C)=78.69 > ti-6111b(B)=78.47
|
||||
→ A>D>C>B (B가 최저, 비단조)
|
||||
```
|
||||
- 데모는 **각 태그 = SP + 일정한 변동량 반복** 시뮬레이션 → temp 순서는 물리 공간구배가 아니라 **각 태그 SP + 변동 위상**의 산물. **공간 구배 미인코딩** → 물리적 단조성 없음 (메모리 `project_demo_system_synthetic_data` 부합).
|
||||
- **함의 1**: §10 역전판정(`A≥B≥C`)을 이 데모에 켜면 `B<C`라 **항상 "온도 역전" 오발**. → §10/front **검증엔 운전원이 단조 temps를 주입**해야 함.
|
||||
- **함의 2**: front(A↔D) 부호의 *물리적* 타당성은 데모로 검증 불가(단위테스트로만). 부호 자체는 머지·통과.
|
||||
|
||||
## 운전원 DCS 주입이 필요한 잔여 (override 불가)
|
||||
| 항목 | 필요 주입 | 관찰 |
|
||||
|---|---|---|
|
||||
| P4 transient | feed를 FeedMoveThresholdPerMin 이상으로 step | transient=true, stream valid=false |
|
||||
| S7 과추출(매트릭스) | B pv ≫ 0.03×feed, D+P+B≠feed (realtime_table) | mbState 음의손실/불일치 |
|
||||
| §10/front 물리검증 | temps를 A>B>C>D 단조로 세팅 후 D 가열 | front "상승→환류↑" |
|
||||
|
||||
## 결론
|
||||
- **확정**: P1(LevelDriven=K×feed 무클램프, 원래 우려 재현)·P2(PCT dormant)·P3(pi-6111b 미사용)·P5(mbState, 단 데모 아티팩트).
|
||||
- **보류(운전원 주입 필요)**: P4·S7·front 물리검증.
|
||||
- **신규 이슈**: 데모 temps 비물리(A>D>C>B) — §10/front 라이브 검증의 선결 조건은 단조 temps 주입. (override가 엔진에 닿지 않는 한계와 함께, 자율 검증 범위가 ramp-advisor로 한정됨.)
|
||||
|
||||
## 참조
|
||||
- `plans/안전피드램프-advisory-작업지시서.md` WP2, `docs/안전피드램프-검증시나리오매트릭스.md` S7
|
||||
- `docs/안전피드램프-한계치-브레인스토밍.md` §10
|
||||
@@ -111,12 +111,11 @@ pica-6111 탑정 / REFLUX DIST(R) / TI-6111D / 상부PACKING(정류,D) /
|
||||
- `SimOverrideEnabled=false`(기본)에서 sim 엔드포인트 403, advisor는 live만 사용.
|
||||
- 코드 grep: sim 경로에서 `WriteTagAsync`/SQL write **0건**.
|
||||
|
||||
### ⚠ 한계 (구현 후 확인, 2026-06-01)
|
||||
**Sim Override는 `FeedRampAdvisorService`(ramp-advisor)만 통합. 엔진(`FeedforwardSupervisor.BuildSnapshotAsync`)은 DB를 직접 읽어 override 미반영.**
|
||||
- 결과: **S7(mbState 과추출)·S6(stale) 라이브 검증은 override로 불가** — `/api/ff/advisory`(엔진 산출)는 override 무시.
|
||||
- S7 라이브 = 운전원이 실제 DCS/realtime_table에 합성값 주입 / 또는 Engine 통합테스트. S6 라이브 = stale 태그(운전원) / 또는 단위테스트(`pv.Feed.Good=false`, 구현됨).
|
||||
- 라이브 override로 검증 가능한 건 **S1~S5(ramp-advisor 경로)뿐** — 전부 통과 확인(2026-06-01).
|
||||
- 후속 옵션: 엔진 스냅샷 빌드도 `ISimOverrideStore` 경유하도록 확장하면 S6·S7도 override로 자율 검증 가능(현재 미적용).
|
||||
### 한계 → 엔진 확장으로 일부 해소 (2026-06-01)
|
||||
초기: Sim Override가 `FeedRampAdvisorService`(ramp-advisor)만 통합, 엔진 미반영.
|
||||
**확장 적용**: `FeedforwardSupervisor.BuildSnapshotAsync`의 `Sample`/`SampleExact`가 override 우선 → `/api/ff/advisory`(엔진 산출)도 override 반영. **안전가드**: `_sim.Enabled` 시 auto-write 억제(가짜 입력이 실제 OPC 쓰기 유발 방지).
|
||||
- **해소**: S7(mbState 과추출)·§10/front 물리검증은 이제 **override로 자율 가능**(B·temps 주입 → 다음 supervisor tick~2s 반영).
|
||||
- **여전히 불가**: S6(stale) — override는 항상 Good=fresh라 stale 시뮬 불가(단위테스트 `pv.Feed.Good=false`로 커버). P4(transient) — `FeedMoveThresholdPerMin=0`이라 config 임계 설정 필요(override만으론 미발화).
|
||||
|
||||
### 실행자 사용 예 (자율 루프)
|
||||
```
|
||||
|
||||
@@ -17,6 +17,7 @@ public sealed class FeedforwardSupervisor : BackgroundService
|
||||
private readonly IFeedforwardWriteGuard _writeGuard;
|
||||
private readonly ILogger<FeedforwardSupervisor> _logger;
|
||||
private readonly Microsoft.Extensions.Configuration.IConfiguration _appConfig;
|
||||
private readonly ISimOverrideStore _sim; // WP0 확장: 엔진 스냅샷 입력 치환(DEMO)
|
||||
private readonly Dictionary<int, ColumnState> _states = new();
|
||||
// Phase II: 마지막 쓰기 시각(스트림별 rate-limit) 및 결과
|
||||
private readonly ConcurrentDictionary<(int colId, string streamKey), DateTime> _lastWriteTimes = new();
|
||||
@@ -26,8 +27,9 @@ public sealed class FeedforwardSupervisor : BackgroundService
|
||||
IServiceScopeFactory scopeFactory, FeedforwardEngine engine,
|
||||
IFeedforwardAdvisoryStore store, IFeedforwardWriteGuard writeGuard,
|
||||
ILogger<FeedforwardSupervisor> logger,
|
||||
Microsoft.Extensions.Configuration.IConfiguration appConfig)
|
||||
{ _scopeFactory = scopeFactory; _engine = engine; _store = store; _writeGuard = writeGuard; _logger = logger; _appConfig = appConfig; }
|
||||
Microsoft.Extensions.Configuration.IConfiguration appConfig,
|
||||
ISimOverrideStore sim)
|
||||
{ _scopeFactory = scopeFactory; _engine = engine; _store = store; _writeGuard = writeGuard; _logger = logger; _appConfig = appConfig; _sim = sim; }
|
||||
|
||||
// Phase II: 쓰기 결과 조회 (Controller에서 사용)
|
||||
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 res = _engine.Tick(cfg, snap, st, DateTime.UtcNow);
|
||||
// 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);
|
||||
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);
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -184,6 +189,8 @@ public sealed class FeedforwardSupervisor : BackgroundService
|
||||
TagSample Sample(string 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)
|
||||
&& double.TryParse(r.LiveValue, NumberStyles.Float, CultureInfo.InvariantCulture, out var v))
|
||||
{
|
||||
@@ -197,6 +204,8 @@ public sealed class FeedforwardSupervisor : BackgroundService
|
||||
TagSample SampleExact(string rawTag)
|
||||
{
|
||||
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)
|
||||
&& double.TryParse(r.LiveValue, NumberStyles.Float, CultureInfo.InvariantCulture, out var v))
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user