Files
ExperionCrawler/plans/안전피드램프-후속작업-작업지시서-WP4-7.md
windpacer 49cf04569e feat: WP5 2·3단계 — 조성 base + 유계 trim (B_SP=분율×feed+편차, advisory)
- ApplyCompositionTrim: LevelDriven 드로우 유계 trim(±5% clamp, B=하부front dev·D=상부front dev, 과도/역전 시 0)
- CompositionStore + /api/ff/composition(랩 분율 수동입력) → supervisor가 TargetCoeff 치환(없으면 config K)
- StreamAdvisory.{CompositionBase,Trim,RecommendedSpComposition,TrimSource} + 컨트롤러·ff.js 표시
- 단위 4건(49/49). advisory·쓰기없음. 게인·부호·분율·rate는 현장 calibrate(데모 검증불가)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 21:19:07 +09:00

13 KiB
Raw Permalink Blame History

작업지시서 — 안전 피드램프 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에 연동 + 코러보레이션 (난이도 中) 2026-06-01

목적

온도 역전/붕괴를 전환류 복귀 트리거 severity에 추가하되, 단발 센서이상이 곧장 recovery를 트리거하지 않도록 코러보레이션(ΔP/vloss 동반 시에만 공정으로 인정).

단계

  1. FeedforwardEngine.ApplyRecovery 시그니처에 string? tempProfileState 추가. Tick에서 tp.State 전달(JudgeTempProfile 결과; 이미 계산됨).
  2. 신호 정의:
    • sigInv = tempProfileState=="온도역전", sigCollapse=="프로파일붕괴".
    • 코러보레이션: corroborated = sigVloss || sigDp(기존 severity 신호).
    • tempSevere = (sigInv||sigCollapse) && corroboratedsevere |= 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 선행 권장) 2026-06-01

목적

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) (난이도 高) — 완료 2026-06-01 (1·2·3단계)

1단계: 2-point front(ApplyFront2Point, 상부 ΔT(CD)·하부 ΔT(CB), C=pivot temps[n-2], 중립 상태). 2단계: ApplyCompositionTrim — LevelDriven 드로우에 유계 trim(±5% clamp, B=하부dev·D=상부dev). 과도/역전 시 trim 0. StreamAdvisory.{CompositionBase,Trim,RecommendedSpComposition,TrimSource}. 3단계: CompositionStore(랩 분율 수동입력, /api/ff/composition GET·POST·DELETE) → supervisor가 LevelDriven TargetCoeff 치환(없으면 config K). B_SP = 분율×feed + trim. 단위 4건 추가(49/49 통과). advisory(쓰기 없음). 현장 calibrate 필요(데모 검증 불가): trim 게인(현 1%/°C placeholder)·부호·조성 분율값. rate제한(듀티 대역폭)은 후속(현재 clamp만).

목적

LevelDriven 드로우 권장을 K×feed(무한상승)에서 [조성목표(분율×feed)] + [bounded 편차 trim] 구조로. 편차는 §8.4 2-타임스케일.

선행 결정 (확정 2026-06-01)

  • 조성 분율 = 랩/원료분석 수동입력: 배치별 중비물/경비물 분율을 운전원 입력(신규 필드/태그). base = 분율×feed.
  • D 처리 = ΔT(CD) 그대로 사용: TI-6111D는 "오염" 아님 — (상승 기상물+환류 비산액) 혼합으로 하부 대비 ~10°C 낮은 유효온도(정정). 상부 front에 D 정상 사용.
  • 편차 clamp = ±5%(보수) + rate ≤ 듀티 대역폭(상호작용 방지).
  • config role = LevelDriven 유지 + trim은 advisory 표시만(쓰기 없음). 재분류·level_tag 강등 안 함.

2-point front 부호 spec (C=제품 pivot=temps[n-2], 하단→상단 [A,B,C,D])

  • 하부(B/중질) = ΔT(CB)=temps[n-2]temps[n-3] (긴 중간 패킹). 정상 C<B(음수). 중질 상승→C 가열→CB↑(dev>0)→B↑.
  • 상부(D/경질) = ΔT(CD)=temps[n-2]temps[n-1] (상부 정류 패킹). 정상 C>D(양수). 경질 침투→CD↓(dev<0)→환류↑. (방향 calibrate.)
  • 각 별도 FrontPositionIndicator(ColumnState 2개). n≥3 필요. trim(2단계): 하부dev→B, 상부dev→D, ±5% clamp + rate≤듀티대역폭. 데모는 단조 temps override로 calibrate.

단계 (결정 후)

  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) (난이도 中, 독립 진행 가능) 2026-06-01

목적

운전원 화면에 ① 피드 램프 계산기 ② 온도 프로파일 상태(역전) 표시 ③ (개발용) 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(역전).