# 작업지시서 — operator-assist 웹 패키징 (2026-06-06)

> 전체설계 `docs/학습형제어-오퍼레이터모방-플랜.md`, 이전단계 `docs/작업지시서-학습형제어-다음단계.md`.
> 메모리 `~/.claude/.../memory/learned-control-operator-imitation.md`.

## 0. 컨텍스트 (필독)

- **오프라인 분석 완주**: 7 train(6-1·6-2·8·9-1·9-2·10-1·10-2) prodmap·shadow·rolling·startup·shutdown 완료. 5차=운전부재 제외. 10차 둘=시운전 신뢰낮음.
- **선결 클린징 완료(2026-06-06)**: `gen_instrument_ranges.py`→`instrument_ranges.json`(태그별 계기 EU range). `c6111_extract.clip_to_ranges`가 range밖 스파이크→NaN. 전 컬럼 envelope 정상화(9-2 feed 558332→453.6). 8차 부수개선 R²0.630→0.831.
- **range 소스 원칙**: realtime_table `PV_HighRange/LowRange`(실측, online 5·6·8차 권위) > xlsx `InstructionsDisplayNumber`(미연결 9·10차 fallback). ★**C# 운영엔 xlsx 하드코딩 금지** — realtime live로 읽음.
- **(a)+(b) 통합 이유**: 라이브 advisory 시각화는 (a)패키징과 (b)C#live가 한 덩어리. 분리하면 (a)는 그릴 라이브데이터 없고 (b)는 화면 없음.
- **차트 라이브러리 기보유**: `echarts.min.js`(trend.js 사용, 산점/막대/정렬), `uPlot.iife.min.js`(fast.js, 조밀 시계열 33만점). 신규 의존성 0.

## 목표
정적 png 분석을 **라이브 advisory 웹 대시보드 + 백테스트 차트**로. write 금지(advisory_only).

---

## 레이어 ① Python 좌표 JSON export  [(b) 무관, 먼저 가능]

**목적**: png 만들던 분석이 좌표 배열 JSON도 동시 export → 웹이 그대로 렌더.

**단계**:
1. 각 분석 스크립트(`c6111_prodmap`/`startup`/`operator_assist`)에 `--json` 출력 추가, 또는 신규 `export_plotdata.py`로 일괄.
2. 컬럼별 `c{prefix}_plotdata.json` 산출:
   - prodmap: 운전점 산점(feed,steam_flow), valve곡선(op,flow_up/dn), pred-vs-actual, feature importance.
   - startup: 컷인정렬 다중에피소드 시계열(rel분, reb/steam/reflux/feed), 컷인 트리거(reb-A 84.6±0.5, ΔT 1.9±0.4).
   - shadow: advisory-vs-actual OP **다운샘플(LTTB or 6h)**, error 히스토그램 bins(서버집계).
3. 33만행 원본 금지 — 반드시 서버 다운샘플.

**검증**: JSON으로 matplotlib png 재현 가능.

---

## 레이어 ② C# 백엔드  [(b) 본체]

**현 상태**: `SteamAdvisor.cs` Predict단건 + `/api/steam/predict`(수동입력)만. **live연결·로깅·UI 전무**.

**단계**:
1. **라이브**: 실시간 태그(feed FICQ-x101, product x118, T_C TI-x111C) 자동 read→`Predict`→시계열 버퍼/`FfTrackingStore` 로깅. `GET /api/steam/live`(현재 모드/권장OP/신뢰도/최근N분 추이). range=realtime `PV_HighRange/LowRange` live.
2. **백테스트**: `c{prefix}_plotdata.json` 서빙 `GET /api/steam/backtest/{col}`.
3. 모드분류(PROD/STARTUP…) + OOD게이트 C#에 이미 포팅됨(envelope=데이터quantile, xlsx 아님).

**검증**: live 태그로 advisory 산출·로깅. OOD시 "범위밖→수동" 표시. write 0건.

**주의**: 6차만 online(C3). 9·10차(C4)는 데모 미연결 → 백테스트 차트만, 라이브 패널 6차 우선.

---

## 레이어 ③ 프론트  [②후]

**단계**:
1. `wwwroot/js/steam.js` + `panes/steam.html`, index.html 등록.
2. **라이브 패널**(uPlot): 권장 vs 실제 OP 추이, 모드/신뢰도 배지, 권장OP 게이지.
3. **백테스트 탭**(ECharts): prodmap 산점, startup 정렬, shadow 시계열+히스토그램. 컬럼 선택.
4. 호버 툴팁(feed/product/T_C/권장OP), 줌/팬.

**검증**: png 수준 그래픽 + 인터랙티브. 컬럼 전환 동작.

---

## 권장순서
①(Python export) → ②(C# live+백테스트 API) → ③(프론트). ①은 (b) 무관해 즉시 착수 가능.

## 검증기준 (전체)
- 라이브: 6차 advisory 표시·로깅, OOD폴백, write 0.
- 백테스트: 전 컬럼 차트 png 동급.
- first-cut 라이브 컬럼: **6-1차**(shadow94%·advisory92.2% 최상, C3 online).

---

## 진단보고서 (diagnosis-checklist.md STEP 1-8 완료, 2026-06-06)

### 맥락 (STEP 1-2)

| 계층 | 진단 대상 | 상태 |
|------|----------|------|
| ① Python export | `export_plotdata.py` 없음. 모든 분석 스크립트 PNG만 출력 | **미착수** |
| ② C# backend | `SteamAdvisor.cs` Predict+controller 있음. live/backtest API 없음 | **일부 완료** |
| ③ Frontend | `steam.js`/`steam.html` 없음, index.html 미등록 | **미착수** |

관련 문서: `docs/학습형제어-오퍼레이터모방-플랜.md`(설계), `docs/작업지시서-학습형제어-다음단계.md`(이전단계). 전 단계 작업4에서 SteamAdvisor.cs+컨트롤러까지 포팅 완료.

### 호출 계층 지도 (STEP 3-4)

```
# C# 현황
GET /api/steam/predict?feed=&product=&tC=
  → SteamAdvisorController.Predict()
    → SteamAdvisor.Predict()
      → ClassifyMode() (hardcoded thresholds)
      → InEnvelope()   (envelope dict)
      → PolyVal()      (3차 valve inverse)
    → return SteamAdvisoryResult

# Layer ② 구축 필요
Hc900RealtimeService (1s polling) → realtime_table (FICQ-x101/x118/TI-x111C)
  → 새로운 background service: live read → Predict → 버퍼/로깅
  → GET /api/steam/live       (현재 advisory)
  → GET /api/steam/backtest/{col} (plotdata.json 서빙)

# Layer ③ 구축 필요
index.html → steam.js + steam.html
  → live 패널 (uPlot) : 권장OP vs 실제 OP 추이
  → backtest 탭 (ECharts) : prodmap/startup/shadow 차트
```

### 체크리스트 결과 (STEP 5-7)

#### 🔴 HIGH (0건)
해당 없음 — 모든 작업은 신규 개발, 기존 운영코드 변경 없음.

#### 🟠 MED (2건)

##### M1. Predict 파라미터 검증 없음

**문제**: `SteamAdvisorController.Predict()`에서 feed/product/tC에 `[Required]`·`[Range]` 미적용. 기본값 0이 그대로 전달되어 NaN이 아닌 0으로 예측 수행 → "정상"으로 오인 가능.
**근거**: `SteamAdvisorController.cs:24-27` — `[FromQuery] double feed`에 검증 속성 없음.
**영향**: 잘못된 입력(0, 음수)이 조용히 Predict 통과 → 신뢰도 HIGH로 표시되어 오퍼레이터 혼동.
**수정**: `[FromQuery][Required][Range(0,2000)] double feed` 등 검증 추가, 실패 시 400 반환.

##### M2. ClassifyMode 임계값 컬럼 종속 — 다중 컬럼 대응 필요

**문제**: `ClassifyMode(feed, product, tC)`가 6-1차 하드코딩(product>100=PROD, feed>50=PROD, tC>60=LINEOUT). 8차(공급원 C4) 등 다른 컬럼은 product 스케일·feed 범위가 다름.
**근거**: `SteamAdvisor.cs:121-127` — 임계값 상수. `SteamModel.Column` 속성은 존재하나 미사용.
**영향**: 8/9/10차 백테스트 차트에서 모드분류 오동작 → 잘못된 advisory 표시.
**수정**: `SteamModel`에 `ModeThresholds { FeedProdMin, ProductProdMin, TcLineoutMin }` 추가, `ClassifyMode`가 모델별 임계값 사용. 6-1 기본값 유지.

#### 🟡 LOW (3건)

##### L1. 모델 경로 절대경로 하드코딩

**문제**: `appsettings.json`과 `SteamAdvisor.cs` 기본값 모두 `/home/windpacer/projects/hc900_ax/...` 절대경로. 배포 서버에서 동작 불가. 또한 단일 모델만 로드 — 백테스트 API에서 컬럼 전환 불가.
**근거**: `appsettings.json:68`, `SteamAdvisor.cs:44`.
**영향**: 배포 시 수동 경로 수정 필요. 백테스트는 별도 컬럼별 모델 로드 로직 추가 필요.
**수정**: (a) 상대경로 `scripts/analysis/{column}_model.json` 패턴 사용, (b) `LoadModel(path)`로 동적 로드 지원(이미 있음), (c) 백테스트용 `GetModel(column)` 추가.

##### L2. Python 스크립트 BASE 경로 하드코딩

**문제**: `c6111_prodmap.py:20`, `c6111_operator_assist.py:14`, `c6111_shadow.py:15` 등 모든 분석 스크립트가 `BASE = "/home/windpacer/.../scripts/analysis/"` 절대경로. `--data` 인자로 우회 가능하나 기본값이 깨짐.
**근거**: 각 스크립트의 `BASE` 상수.
**영향**: 다른 환경에서 `--data` 생략 시 FileNotFoundError. PNG 저장도 BASE 경로에 고정.
**수정**: `os.path.dirname(os.path.abspath(__file__))` 기반 상대경로 또는 인자화.

##### L3. PredictAsync가 동기 래퍼

**문제**: `PredictAsync()`가 `ValueTask.FromResult(Predict(...))` 반환 — 실제 비동기 동작 없음. CPU-bound이므로 sync가 적절하나 Async 접미사 오해 소지.
**근거**: `SteamAdvisor.cs:115-119`.
**영향**: 성능 영향 없음. 호출자 입장에서 async로 오인 가능.
**수정**: (현상 유지 권장) 주석 추가 또는 sync `Predict()` 직접 호출.

### 교차 검증 완료 (STEP 6)

| 항목 | Q1. 이미 수정? | Q2. 타 레이어 처리? | Q3. 의도적 설계? | Q4. 재현 시나리오? | 판정 |
|------|:---:|:---:|:---:|:---:|:---:|
| M1. 파라미터 검증 | 아니요 | `SteamAdvisorController` 자체에 없음 | 아니요 | feed=0 전송 시 0으로 예측 → HIGH 표시 | **MED** |
| M2. 모드 임계값 | 아니요 | `SteamAdvisor` 단독 | 6-1 pilot은 의도적 | 8차 백테스트에서 PROD 미인식 | **MED** |
| L1. 모델 경로 | 아니요 | `IConfiguration` 주입 | 아니요 | 백테스트 컬럼 전환 시 FileNotFound | **LOW** |
| L2. BASE 경로 | 아니요 | CLI 기본값 | 편의상 | `--data` 생략 시 오류 | **LOW** |
| L3. PredictAsync | 해당 없음 | 해당 없음 | CPU-bound sync = 의도적 | 없음 | **제외(Q3)** |

### 자가 검증 (STEP 8)

- [x] 각 지적 사항을 현재 파일 몇 번 줄로 직접 가리킴
- [x] HIGH 항목 없음 — 신규 개발이므로 기존 운영코드 변경 없음
- [x] 교차 검증 4개 질문 모두 통과한 항목만 포함
- [x] 수정 예시가 현재 코드에 아직 적용되지 않은 내용
- [x] "더 좋은 방법 제안"과 "현재 코드가 틀렸다" 혼동하지 않음

