docs: 후속 작업지시서 WP4-7 (역전→recovery+코러보, 압력서브시스템, 편차trim, 프론트UI)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
windpacer
2026-06-01 17:16:41 +09:00
parent 4e1dfc1879
commit 25fd969276

View File

@@ -0,0 +1,147 @@
# 작업지시서 — 안전 피드램프 Advisory 후속 (WP4~WP7)
> 작성 2026-06-01. 다른 LLM/개발자 단독 실행용. 사전 맥락 없이 이 문서 + 참조로 수행.
> 컬럼 C-6111. **선행 통독 필수**: `docs/안전피드램프-한계치-브레인스토밍.md`(§0~§10), `plans/안전피드램프-advisory-작업지시서.md`(WP0~WP3), `docs/안전피드램프-검증시나리오매트릭스.md`.
---
## 0. 현재 상태 (main, 커밋 ~4e1dfc1까지 완료)
이미 구현·검증된 것:
- **버그픽스**: FeedforwardConfigStore ordinal off-by-one, FeedforwardEngine.ApplyFront front 부호(상단−하단).
- **WP0 Sim Override**: `SimOverrideStore`(ISimOverrideStore), `GET/POST/DELETE /api/ff/sim/override`, `appsettings Feedforward:SimOverrideEnabled=true`(DEMO 게이트). **엔진까지 통합**(`FeedforwardSupervisor.BuildSnapshotAsync` override 우선) + 안전가드(`_sim.Enabled` 시 auto-write 억제).
- **WP3 FeedRampAdvisor**: `FeedRampCalculator`(순수), `FeedRampAdvisorService`, `GET /api/ff/ramp-advisor`, DTO `FeedRampModels.cs`.
- **§10 역전판정**: `TempProfileJudge`(순수), 엔진 `JudgeTempProfile`(탑정 D 제외, spanRef 시드, 역전/붕괴 시 front trim HOLD), `AdvisoryResult.{TempProfileState,InversionPair,TempSpan,TempSpanRef}` + 컨트롤러 노출.
- 테스트 37/37, 라이브 S1~S5·S7·front·역전 검증 완료.
## 0.1 공통 제약 (전 WP)
1. **쓰기 금지**(OPC/제어 SP)는 advisory 전부에 유지. WriteGuard·ARM 경유만 실쓰기(기존). 신규 advisory는 계산/표시만.
2. **DEMO**: 실시간 값 = SP + 일정변동 반복(합성). 물리 동특성 추정 금지. temps는 비물리(공간구배 미인코딩) → 물리 검증은 override로 단조/특정 프로파일 주입해야.
3. **Sim Override로 자율검증**: `POST /api/ff/sim/override {enabled,values:{"tag.pv":v}}` → 엔진/ramp-advisor 모두 반영(~2s tick). 단 stale(S6)은 불가(override=항상 fresh), transient(P4)는 `FeedMoveThresholdPerMin>0` 필요.
4. **JSON**: 신규 응답은 컨트롤러에서 **익명객체 camelCase 매핑**(기존 `MapColumn`/`MapRamp` 패턴). 비유한(NaN/Inf)은 null로.
5. **빌드/테스트**: `dotnet build src/Web/ExperionCrawler.csproj`(경고0/에러0), `dotnet test tests/ExperionCrawler.Tests/ExperionCrawler.Tests.csproj`.
6. **커밋**: 기본 브랜치(main)면 브랜치 먼저. WP별 분리 커밋 권장.
## 0.2 C-6111 핵심 (변경 없음)
- 스트림: P(ficq-6118,Commanded,K0.95,θ60,τ900,sp_max2000,rate30), R(ficq-6113,Commanded,K0.8), D(ficq-6114,LevelDriven,K0.02,lica-6113), B(ficq-6116,LevelDriven,K0.03,li-6111). feed=ficq-6101.
- 컬럼: pressure=pica-6111, **dtdp=0(PCT off)**, delta_p_tag=null, sensitive_tray_tag=null, temp_tags=`tica-6111a,ti-6111b,ti-6111c,ti-6111d`.
- 추가 태그: `pi-6111b.pv`(≈리보일러 진공압), `ti-6103.pv`(프리히터 공급온도), `ficq-6115.pv`(스팀 flow 측정), `tica-6111a.op`(스팀밸브 OP 직결).
- 레이아웃(위→아래): pica-6111/REFLUX DIST(R)/TI-6111D/상부PACKING(정류,D)/제품추출 P(70~75%)/TI-6111C(제품 pivot)/긴중간PACKING(주분리,B)/TI-6111B/PREHEATER DIST(피드,TI-6103)/하부짧은PACKING(stripping)/TICA-6111A 리보일러(pi-6111b≈여기)→B.
- **정상 온도 A>B>C>D 단조감소**. TI-6111D는 환류 서브쿨 오염(조성 proxy 부적합).
## 0.3 코드 색인
| 역할 | 경로 |
|---|---|
| 엔진 | `src/Infrastructure/Control/FeedforwardEngine.cs` (Tick/ComputeStream/ApplyFront/ApplyRecovery/JudgeTempProfile) |
| supervisor | `src/Infrastructure/Control/FeedforwardSupervisor.cs` (BuildSnapshotAsync, AutoWriteAsync) |
| 계산기/판정 | `FeedRampCalculator.cs`, `TempProfileJudge.cs` |
| 모델 | `src/Core/Application/Feedforward/FeedforwardModels.cs`, `FeedRampModels.cs` |
| config store/DDL | `FeedforwardConfigStore.cs`, `src/Infrastructure/Database/ExperionDbContext.cs` |
| 컨트롤러 | `src/Web/Controllers/FeedforwardController.cs` (route api/ff) |
| 프론트 | `src/Web/wwwroot/js/ff.js`, `index.html`, `css` |
| 테스트 | `tests/ExperionCrawler.Tests/` |
---
## WP4 — §10 역전을 ApplyRecovery에 연동 + 코러보레이션 (난이도 中)
### 목적
온도 역전/붕괴를 전환류 복귀 트리거 severity에 추가하되, **단발 센서이상이 곧장 recovery를 트리거하지 않도록 코러보레이션**(ΔP/vloss 동반 시에만 공정으로 인정).
### 단계
1. `FeedforwardEngine.ApplyRecovery` 시그니처에 `string? tempProfileState` 추가. `Tick`에서 `tp.State` 전달(JudgeTempProfile 결과; 이미 계산됨).
2. 신호 정의:
- `sigInv = tempProfileState=="온도역전"`, `sigCollapse=="프로파일붕괴"`.
- **코러보레이션**: `corroborated = sigVloss || sigDp`(기존 severity 신호).
- `tempSevere = (sigInv||sigCollapse) && corroborated``severe |= tempSevere`.
- **비코러보(온도만)**: `sigInv && !corroborated` → severe 아님. 대신 advisory 메시지 "온도역전(센서 점검 권고 — ΔP/물질수지 정상)" 노출(새 필드 또는 modeReason 보강).
3. `SeverityText()``tempSevere`면 "온도역전/붕괴" 추가.
4. (주의) ΔP 코러보는 WP6(pi-6111b→ΔP) 이후에야 유효. 그 전엔 vloss 코러보만 동작 → 코드는 둘 다 지원하되 ΔP 없으면 vloss로.
### 산출물/합격
- 단위테스트(`FeedforwardRecoveryTests`에 추가): ① 역전+vloss불균형 → severe(전환류 진입 경로), ② 역전 단독 → severe 아님 + 센서점검 메시지.
- 라이브: override로 (a)역전+B과추출(vloss) → recovery 트리거(RecoveryEnabled 필요), (b)역전만 → 트리거 안 됨.
- 빌드0/테스트 통과.
---
## WP6 — 압력 서브시스템 깨우기 (§9) (난이도 中, WP4·WP5 선행 권장)
### 목적
pi-6111b를 활용해 **전탑 ΔP**(flooding) + **압력 프로파일 기반 PCT**를 활성화. 현재 dtdp=0·delta_p_tag=null로 dormant.
### 단계
1. **ΔP 파생**: `pi-6111b.pv pica-6111.pv` = 전탑 ΔP.
- 옵션A(권장): supervisor/엔진에서 계산해 `pv.DeltaP`에 주입(현재 `cfg.DeltaPTag`로 읽는 구조). `BuildSnapshotAsync`에서 DeltaPTag 없으면 `pi-6111b pica-6111`로 합성.
- flooding ceiling(ramp-advisor)·ApplyRecovery sigDp가 이 ΔP 사용. `DeltaPFloodLimit`(config, 현재 MaxValue) 운전값으로 설정.
2. **압력 프로파일 PCT**: 현재 `BuildTemps`가 단일 압력(cfg.PressureTag)+dtdp. 확장:
- 각 temp에 **국소 압력** 부여 = 높이 보간(또는 패킹가중): ti-6111d≈pica, tica-6111a≈pi-6111b, ti-6111c/ti-6111b=보간. 보간 비율은 config 또는 const(예: 높이 0/0.33/0.66/1.0 → 실제 표고 미상이라 우선 균등).
- `TempCorrection.PressureCompensated(raw, p_local, pRef_local, dtdp)`. pRef도 프로파일(또는 단일 pRef + 동일 dtdp).
- `dtdp`를 config에서 실측 보정계수로(현재 0). **dtdp=0이면 PCT=raw(무변경)** 유지.
3. config: `delta_p_tag`·`dtdp`·`delta_p_flood_limit`는 기존 컬럼 존재 → 값만 세팅(운전원/UI). 프로파일 보간 비율은 신규 const/config.
### 산출물/합격
- 단위테스트: dtdp≠0 + 프로파일 압력 → 각 temp pct가 국소압 반영(상·하 다른 보정). ΔP 합성 동작.
- 라이브: override로 pica/pi-6111b·temps 주입 → `/api/ff/advisory` temps.pct ≠ raw, flooding ceiling이 합성 ΔP 사용.
- **주의**: PCT 활성화는 §10 역전판정·front 입력을 바꾸므로 WP4 이후 회귀 확인.
---
## WP5 — 편차 trim + 2-point sensitive tray (§8·§8.8) (난이도 高, 설계결정 필요)
### 목적
LevelDriven 드로우 권장을 `K×feed`(무한상승)에서 **[조성목표(분율×feed)] + [bounded 편차 trim]** 구조로. 편차는 §8.4 2-타임스케일.
### ⚠ 선행 결정 (사용자 확인 필요 — 구현 전 질의)
- **조성 분율 입력 경로**: 랩 수동입력 / 온라인 분석기 / KB? 갱신 주기? (base = 분율×feed)
- **D 환류 서브쿨 처리**(§10.2-B): 상부 front에 TI-6111D 직접 사용 불가 → (b1)서브쿨 보정 (b2)C 단독 PCT (b3)D 재지정 중 택1.
- **편차 clamp/rate 한계**: 제거목표의 ±몇 %? rate ≤ 듀티 대역폭(상호작용 방지).
- **config 모델**: B/D를 LevelDriven→Commanded(조성구동) 재분류? li-6111 level_tag 강등?
### 단계 (결정 후)
1. config 확장: `UpperSensitiveTrayTag`+`LowerSensitiveTrayTag`(ff_column_config 신규 컬럼 + ExperionDbContext boot DDL + FeedforwardConfigStore SELECT/INSERT/UPDATE + MapConfig). 또는 섹션별 차온쌍.
2. 2-point front: `FrontPositionIndicator` 2개 인스턴스(ColumnState). 상부=ΔT(TI-6111CTI-6111D보정), 하부=ΔT(TI-6111CTI-6111B). ApplyFront 섹션별 반환.
3. fast trim(초~분): 섹션 front 편차 → bounded trim(clamp+rate). B=하부, D=상부.
4. 귀속: vloss를 "프론트 드리프트하는 스트림"에 배분(상부 고정·하부 이동=B).
5. slow re-baseline(시간): vLossMa+랩분석 → base 분율 재보정(운전원 리뷰, 자동 아님).
6. SP 산출: `B_SP = 분율×feed + trim`(advisory; 쓰기는 기존 정책).
### 산출물/합격
- 단위테스트: 상/하 front 분리, trim 부호·clamp, 귀속.
- 라이브: override로 하부 front 드리프트 → B trim만, 상부 → D trim만.
- **범위 주의**: 가장 크고 설계 미확정. 1단계(2-point front + 표시) → 2단계(trim) → 3단계(조성base SP)로 **분할 권장**.
---
## WP7 — 프론트 UI (ff.js) (난이도 中, 독립 진행 가능)
### 목적
운전원 화면에 ① 피드 램프 계산기 ② 온도 프로파일 상태(역전) 표시 ③ (개발용) sim override 패널.
### 단계
1. **램프 계산기 패널**(`#pane-ff` 또는 카드 상단): 입력 targetFeed/deltaIAllow/sensibleGain/feedTempRef/floodLimit → `GET /api/ff/ramp-advisor?columnId=&...` → 결과표(clampedTarget, ceiling{value,binding}, rampRate{value,binding}, rampTimeMin, steam{from,to}, warnings). camelCase 응답 그대로.
2. **tempProfileState 뱃지**: 카드에 `tempProfileState`(정상/약화/붕괴/온도역전) 색상 뱃지 + `inversionPair`·`tempSpan`. 역전 시 경고색 + frontTrim "HOLD" 표기.
3. **sim override 패널**(개발/데모 전용, `SimOverrideEnabled` 시만 노출): 태그-값 입력 → `POST /api/ff/sim/override`, `DELETE`로 해제. `simOverrideActive` 표시.
4. 기존 ff.js 패턴(`ffCard`, fetch, esc) 재사용. `node -c ff.js` 문법 확인.
### 산출물/합격
- 브라우저에서 램프 계산기 동작(입력→결과), 역전 뱃지 표시, sim 패널로 주입/해제.
- `dotnet build` 영향 없음(정적자산). Ctrl+F5 후 확인.
---
## 권장 순서
1. **WP4**(역전→recovery, 작음) → 2. **WP6**(압력/ΔP/PCT — WP4 코러보 ΔP 공급) → 3. **WP7**(UI, 독립) → 4. **WP5**(편차 trim, 설계결정 후 분할).
## 검증 공통 (sim override 자율 루프)
```
curl -s -XPOST localhost:5000/api/ff/sim/override -H 'Content-Type: application/json' -d '{"enabled":true,"values":{...}}'
sleep 3; curl -s localhost:5000/api/ff/advisory # 엔진 반영
curl -s 'localhost:5000/api/ff/ramp-advisor?columnId=1&targetFeed=1100&...'
curl -s -XDELETE localhost:5000/api/ff/sim/override
```
**앱 재기동** 필요(코드 변경 반영): 운전원이 `dotnet run` 재시작.
## 참조 / 메모리
- 메모리: `project_safe_feed_ramp_brainstorm`, `project_demo_system_synthetic_data`, `reference_json_serializer_pascalcase`, `project_sidedraw_ff_advisory`, `project_realtime_collector_stall`.
- `docs/안전피드램프-한계치-브레인스토밍.md` §7(편차/스팀)·§8(편차소스)·§9(압력)·§10(역전).