feat: 안전 피드램프 Advisor (WP0 Sim Override + WP1 매트릭스 + WP3 계산기)

WP3 — read-only Feed Ramp Advisor (쓰기 없음):
- FeedRampCalculator(순수함수): ceiling(밸브포화/flooding)·램프율(valveSlew/dynamic)·예상시간·binding·스팀(FIQ-6115 목표, a안 현열보정) 산출
- FeedRampModels(DTO+ISimOverrideStore), FeedRampAdvisorService(라이브+override), GET /api/ff/ramp-advisor
WP0 — Sim Override Layer:
- SimOverrideStore(ConcurrentDictionary+volatile), sim/override GET·POST·DELETE, Feedforward:SimOverrideEnabled 게이트
- 한계: ramp-advisor만 통합·엔진 미반영 → S6/S7 라이브는 override 불가(문서화)
WP1 — docs/안전피드램프-검증시나리오매트릭스.md (S0~S7)
검증: 단위 31/31, 라이브 스모크 S1~S5 기대치 일치(3.29 dynamic/60.8min/366.7, 31.58 valveSlew, 2105 clamp, 1018.8 flooding, 309 현열)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
windpacer
2026-06-01 16:22:11 +09:00
parent d1927f2d22
commit 54ca4d0d62
10 changed files with 564 additions and 2 deletions

View File

@@ -0,0 +1,126 @@
# WP1 — 안전 피드램프 Advisory 검증 시나리오 매트릭스
> 작성 2026-06-01. 대상: `plans/안전피드램프-advisory-작업지시서.md` WP3(`FeedRampCalculator`/`/api/ff/ramp-advisor`).
> 역할 3종: ① WP3 구현 spec ② WP3 단위테스트 정의 ③ 라이브 sim(WP0 override) 검증 스크립트.
> 컬럼: C-6111. 모든 기대 수치는 §0 config로 직접 계산(재현 가능).
---
## 0. 기준 상수 (C-6111, ff_stream_config) + 공통식
| key | role | K | θ_up(s) | τ(s) | sp_max | rate_up(kg/hr·min) |
|---|---|---|---|---|---|---|
| P | Commanded | 0.95 | 60 | 900 | 2000 | 30 |
| R | Commanded | 0.80 | 0 | 0 | 2000 | 30 |
| D | LevelDriven | 0.02 | | | 1000 | 0 |
| B | LevelDriven | 0.03 | | | 500 | 0 |
ProductKey=P. 피드=ficq-6101.
```
valveSlew = min_i(rate_up_i/K_i) = min(30/0.95, 30/0.8) = 31.58 kg/hr·min (P)
dynamic = ΔI × 60 / (K_P×(τ_P+θ_P)) = ΔI × 60/912 = ΔI × 0.06579
ΔI=50 → 3.29 ΔI=600 → 39.47
R_feed = min(valveSlew, dynamic, [energyLoop])
valveCeiling = min_i(sp_max_i/K_i) = min(2105.3, 2500, 50000, 16666.7) = 2105.3 (P)
floodCeiling = Fcur × (floodLimit/ΔPnow)^(1/n) ΔPnow = pi-6111b pica-6111
ceiling = min(valveCeiling, floodCeiling[, steamCeiling])
rampTimeMin= (clampedTarget currentFeed) / R_feed
steamTo = fiq6115Cur × (clampedTarget/currentFeed) + sensibleGain × clampedTarget × (feedTempRef ti6103Now)
```
## 0.1 공통 baseline 주입값 (anchor, 평형)
| 태그 | 값 | 비고 |
|---|---|---|
| ficq-6101.pv (feed) | 900 | currentFeed |
| ficq-6118.pv (P) | 855 | ≈0.95×900 |
| ficq-6113.pv (R) | 720 | ≈0.8×900 |
| ficq-6114.pv (D) | 18 | ≈0.02×900 |
| ficq-6116.pv (B) | 27 | ≈0.03×900 |
| ficq-6115.pv (스팀) | 300 | fiq6115Cur |
| ti-6103.pv | 150 | = feedTempRef(앵커) |
| pica-6111.pv | 40 | 탑정 진공압(mmHg) |
| pi-6111b.pv | 80 | 탑저 진공압 → ΔPnow=40 |
허용오차: 비율·flow ±2%, 시간 ±2%, binding 라벨은 정확일치.
---
## 1. 시나리오 매트릭스 (S0~S7)
| ID | 목적 | 주입/파라미터(anchor에서 변경분) | 기대 출력 | 합격기준 | 검증경로 |
|---|---|---|---|---|---|
| **S0** | baseline 평형 | targetFeed=**900**, ΔI=50 | clampedTarget=900, rampTimeMin≈0, ceiling=2105.3(**P**), steamTo=300(무변화), warnings≠∅ | rampTime≈0, ceiling 2105±2% binding=P, steam 300±2% | 단위+sim |
| **S1** | 피드 증량(동특성 binding) | targetFeed=**1100**, ΔI=50, feedTempRef=150, sensibleGain=0.001 | R_feed=**3.29**(binding=**dynamic**), clampedTarget=1100, rampTimeMin=**60.8**, steamTo=**366.7**, ceiling=2105.3(P) | rampRate 3.29±2% binding=dynamic, time 60.8±2%, steam 366.7±2% | 단위+sim |
| **S2** | 밸브 슬루 binding | targetFeed=1100, **ΔI=600** | dynamic=39.5 > valveSlew=31.58 → R_feed=**31.58**(binding=**valveSlew@P**), rampTimeMin=**6.33** | rampRate 31.58±2% binding=valveSlew@P, time 6.33±2% | 단위+sim |
| **S3** | ceiling 초과(밸브포화) | targetFeed=**2200**, ΔI=50 | clampedTarget=**2105.3**(binding=**P sp_max**), R_feed=3.29, rampTimeMin=**366.4** | clampedTarget 2105±2%, ceiling.binding="P sp_max", targetFeed 원값 2200 표기 | 단위+sim |
| **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×(150140)=**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) |
---
## 2. Worked 계산 (재현)
```
S1: dynamic=50×0.06579=3.29 valveSlew=31.58 → R_feed=3.29(dynamic)
clampedTarget=min(1100,2105.3)=1100 rampTime=(1100900)/3.29=60.8 min
steam=300×(1100/900)+0.001×1100×(150150)=366.7+0=366.7
S2: dynamic=600×0.06579=39.47 valveSlew=31.58 → R_feed=31.58(valveSlew@P)
rampTime=(1100900)/31.58=6.33 min
S3: clampedTarget=min(2200,2105.3)=2105.3(P sp_max)
rampTime=(2105.3900)/3.29=366.4 min
S4: ΔPnow=12040=80 floodCeiling=900×(100/80)^(1/1.8)
(100/80)=1.25 1.25^0.5556=1.1317 → 1018.5
ceiling=min(2105.3,1018.5)=1018.5(flooding) clampedTarget=1018.5
rampTime=(1018.5900)/3.29=36.0 min
S5: steam=300×(900/900)+0.001×900×(150140)=300+9.0=309.0 (피드 무변, 현열만)
S7: vloss=feed(D+P+B)=900(18+855+150)=123 (|123|>3%·900=27) → "음의 손실(스팬 오류 의심)"
```
---
## 3. 라이브 sim 검증 (WP0 override 경로)
공통 anchor 주입(S1 예):
```
curl -s -XPOST :5000/api/ff/sim/override -H 'Content-Type: application/json' -d '{
"enabled":true,
"values":{"ficq-6101.pv":900,"ficq-6118.pv":855,"ficq-6113.pv":720,
"ficq-6114.pv":18,"ficq-6116.pv":27,"ficq-6115.pv":300,
"ti-6103.pv":150,"pica-6111.pv":40,"pi-6111b.pv":80}}'
curl -s ':5000/api/ff/ramp-advisor?columnId=1&targetFeed=1100&deltaIAllow=50&feedTempRef=150&sensibleGain=0.001'
# 기대: rampRate.value≈3.29 binding=dynamic, rampTimeMin≈60.8, steam.fiq6115To≈366.7, ceiling.value≈2105 binding=P
curl -s -XDELETE :5000/api/ff/sim/override
```
- S2: 같은 anchor + `deltaIAllow=600`
- S3: `targetFeed=2200`
- S4: override에 `pi-6111b.pv=120` + query `floodLimit=100&n=1.8`
- S5: override에 `ti-6103.pv=140` + query `targetFeed=900&sensibleGain=0.001&feedTempRef=150`
- S7: override에 `ficq-6116.pv=150``GET /api/ff/advisory`의 massBalanceState 확인(ramp-advisor 아님)
## 4. 단위테스트 매핑 (WP3-F)
| 시나리오 | 테스트 | 비고 |
|---|---|---|
| S0~S5 | `FeedRampCalculatorTests` (합성 ColumnConfig+PvSnapshot 직접 구성) | S5는 sensibleGain·feedTempRef 명시 전달 |
| S6 | 동상 단위테스트, `pv.Feed.Good=false` → HOLD 검증 | |
| S7 | **제외** — mbState는 `FeedforwardEngine` 산출. WP2 또는 Engine 통합테스트 | 작업지시서 §256 |
## 5. 합격 종합
- S0~S5 단위테스트 전건 통과(허용오차 내), binding 라벨 정확.
- 라이브 sim S1~S5가 단위테스트 기대치와 일치(override↔계산 동일 입력).
- S6 HOLD, S7 mbState≠정상.
- 모든 미산정(energyLoop·flooding 근거 부재 등) warnings 노출.
## 6. 참조
- `plans/안전피드램프-advisory-작업지시서.md` (WP0~WP3)
- `docs/안전피드램프-한계치-브레인스토밍.md` §3(공식)·§7(스팀)·§9(압력)