# 작업지시서 — 학습형 제어 다음 단계 (6-1차 이후)

> 작성 2026-06-05. 전체 설계·진행로그는 `docs/학습형제어-오퍼레이터모방-플랜.md`(특히 §0 오리엔테이션, §15 데이터, §16 분석).
> 메모리: `~/.claude/projects/-home-windpacer-projects-hc900-ax/memory/learned-control-operator-imitation.md` (Claude 메모리 — **세션 시작 시 MEMORY.md 인덱스로 자동 로드**. 리포지토리 밖이라 cwd에서 `ls`로는 안 보임). 이 문서는 **남은 4개 작업의 실행 지시서**.

---

## 🔍 진단 결과 (diagnosis-checklist.md 규칙 적용, 2026-06-05)

**진단 대상**: `docs/작업지시서-학습형제어-다음단계.md`  
**방법**: `diagnosis-checklist.md` 8단계 적용 (STEP 1 맥락 → 2 구조탐색 → 3 코드읽기 → 4 호출계층 → 5 체크리스트 → 6 교차검증 → 7 심각도 → 8 보고서)

### 항목 1. ~~참조 메모리 파일 미존재~~ → 오탐 (제거)

**오탐 사유**: `learned-control-operator-imitation.md`는 `/home/windpacer/.claude/projects/-home-windpacer-projects-hc900-ax/memory/`에 정상 존재(5144B). Claude 메모리 디렉토리(리포지토리 밖)에 있어 `cwd` 기준 `ls`로는 발견되지 않았음.  
**STEP 6 Q1 교차검증 탈락**: 파일 실재 → 보고서에서 제거.  
**보강 제안**: line 4에 전체 경로를 명시하면 다음 세션 컨텍스트 복원에 유리함.

### 항목 2. DB 스키마 구분 불명시 (MED)

**문제**: 분석용 DB(`field_hist`)는 `public` schema만 있고 `hc900` schema는 `iiot_platform` DB 소속. `hc900.ff_column_config`는 `iiot_platform` DB에만 존재(C-6111 단독). line 18이 `hc900.ff_column_config`를 마치 `field_hist`에서 조회 가능한 자원인 것처럼 서술하고 있음.  
**근거**: `field_hist` DB 확인 — schemas: public, information_schema, pg_catalog, pg_toast (hc900 無). `iiot_platform` DB 확인 — `hc900.ff_column_config` → `(1, C-6111, t)` 단독.  
**영향**: 작업1에서 "ff_column_config엔 C-6111만 있을 가능성"이라는 가정은 확인됨(사실). 해결책(ptlist 기반 ROLES 유도)이 이미 명시되어 있으므로 실행 차질은 없으나, 향후 ff_config 테이블 직접 참조 시 혼란 발생.  
**수정**: line 18에 "`hc900.ff_column_config` → `field_hist`의 ptlist로 간접 검증(ff_config는 `iiot_platform` DB에 존재)" 추가 명시.

### 항목 3. `run_column.py` 경로 미명시 (LOW)

**문제**: 작업1 step 3가 `run_column.py` 래퍼를 만들라고 지시하나, 위치(scripts/analysis/? scripts/?) 불명시.  
**근거**: `docs/작업지시서-학습형제어-다음단계.md:41`  
**영향**: 구현 시 경로 결정이 미뤄짐. 다른 분석 스크립트와의 정합성(import 경로, BASE 상수)이 깨질 위험.  
**수정**: `scripts/analysis/run_column.py`로 위치 명시 권장.

### 항목 4. `predict(live_tags)` 입력 포맷 미명시 (LOW)

**문제**: 작업3 step 1의 `predict(live_tags)`가 받는 `live_tags`의 데이터 구조(dict? 키 형식? gRPC 태그명?)가 정의되지 않음.  
**근거**: `docs/작업지시서-학습형제어-다음단계.md:79`  
**영향**: 작업3 착수 시 인터페이스 결정 필요. C# 포팅(작업4)의 `SteamAdvisor`와 인터페이스 불일치 가능.  
**수정**: 입력 구조 예시 추가: `{"feed": 534.2, "product": 318.5, "T_C": 84.7}` 또는 gRPC `ReadTagsResponse` 참조.

### 항목 5. 작업4 모델 포맷 복수 경로 리스크 (LOW)

**문제**: 작업4 step 1이 "JSON 계수/룩업 또는 ONNX"로 포맷을 열어둠. 두 갈래 중 선택 지연 시 C# 포팅 시작 전 방향성 분산 위험.  
**근거**: `docs/작업지시서-학습형제어-다음단계.md:101`  
**영향**: Python export ↔ C# import 파이프라인이 병렬로 갈 수 있음.  
**수정**: 문서에서 1안(GBM 계수→C# 직접 계산)을 권장하고 ONNX는 모델 복잡도 증가 시 fallback으로 명시.

### 교차 검증 통과 내역

| 항목 | Q1 (이미수정?) | Q2 (타레이어?) | Q3 (의도적?) | Q4 (재현?) |
|------|:---:|:---:|:---:|:---:|
| #1 ~~메모리 파일~~ | 오탐 | 오탐 | 오탐 | 오탐 |
| #2 DB 스키마   | No | 부분완화 | No | Yes |
| #3 run_column  | No | No | No | 부분 |
| #4 predict 포맷 | No | No | No | 부분 |
| #5 모델 포맷   | No | No | No | 부분 |

---

## 0. 현재까지 (필독 컨텍스트)

✔ **6-1차 (C-6111, 오프라인 분석 완주):**
- ① 생산제어: GBM R²0.993, shadow 94% 모방, 롤링 MAE 1.17%, startup 컷인 3건 clean.
- ② START-UP: ★컷인 트리거 reb-A 84.6±0.5℃ & ΔT(A-D) 1.9±0.4℃.

✔ **6-2차 (C-6211, 2026-06-05 완료):**
- GBM R² **0.997**(전 컬럼 최고), PROD 98.8%, steam/feed=0.623.
- shadow 79.7% (OOD 53%), 롤링 OP MAE **1.07%**(최고).
- startup 5건, shutdown 5건.

✔ **8차 (C-8111, 2026-06-05 완료):**
- GBM R² **0.630**(PROD 59.4%), shadow 66.9%, valve stiction 2.8%.
- startup 1건(reb-A 96.2℃), shutdown 1건(ΔT 17℃).

✔ **9차 (C-9111, 2026-06-05 완료):**
- GBM R² 0.886, steam/feed=0.929. shadow 63.5%.
- startup 25건 과다탐지, shutdown 22건(3개 하위유형).

✔ **10차 (C-10111, 2026-06-05 완료):**
- PROD **1.7%**, 분석 신뢰도 낮음. rolling 5월 이전 조기종료.

✔ **SHUTDOWN (2026-06-05, 작업2 완료):**
- `detect_cutoffs()` — product>100→<50 + steam 동반 하강.
- 6-1(4건 일관): ★셧다운 트리거 reb-A=84.7℃, steam 1분→진공 4분→냉각 140분+.
- 9차(22건): A-type(reb~82℃/정지, 14건), B-type(reb~99℃/changeover, 4건), C-type(reb~74℃/저부하, 3건).

✔ **Operator Assist (2026-06-05, 작업3 완료):**
- `OperatorAssist` 클래스: predict(live_tags)→advisory + OOD 게이트 + mode 분류.
- `c6111_operator_assist.py` — `--data`/`--prefix` CLI로 모든 컬럼 호환.
- Advisory 성능: 6-1 \|Δ|≤2% **92.2%** ✅, 6-2 **93.1%** ✅(목표 90%+ 달성).
- 8(84.3%)·9(80.8%)·10(61.9%)는 PROD 데이터 부족으로 미달.

**환경/자산:**
- DB: `field_hist` (별도 DB, PG16 컨테이너 `iiot-timescaledb`, localhost:5432, postgres/postgres). shinam 실데이터 2026-02~06 ~30초. WIDE 포맷(§15.2~15.3 디코드).
- Python: `mcp-server/.venv/bin/python` (psycopg3=`psycopg`, pandas, sklearn1.8, matplotlib. **psycopg2·pyarrow 없음** → pickle 사용).
- 코드: `scripts/analysis/c6111_*.py` — **재사용 추출기 `tag_frame(conn, role_map)`** (c6111_extract.py), `SteamPredictor`(c6111_shadow.py).
- C-6111 토폴로지: 기존 `hc900.ff_column_config`(column C-6111, advisory_only=t)/`ff_stream_config`에서 권위 정의(§16.1). ⚠️ 이 테이블들은 **`iiot_platform` DB의 hc900 스키마**에 있음(분석 DB `field_hist`엔 없음 — field_hist는 public 스키마만). 형제 컬럼(6-2·8·9·10)은 ff_config 행이 없을 수 있어 ptlist 네이밍으로 ROLES 유도(작업1).

**핵심 gotcha:**
- 컬럼 디코드: `ptname→ptlist.pid→mapping(tid,oit)→cont{tbl}.col{oit:02d}`, 시간축 `dtat`.
- **운전점 집계가 필수**: 정상상태 98%라 점단위 회귀는 음의 R² → 6h 중앙값으로 집계해야 맞음(§16.6).
- **OOD 게이트 필수**: 새 로드레짐은 외삽 실패 → 롤링 재학습 + OOD→오퍼레이터 폴백(§16.7~8).
- pandas `df.product`는 메서드와 충돌 → `df["product"]`. col은 0패딩(`col03`). `pd.read_sql`는 psycopg3에 경고만(동작OK).

---

## 작업 1 — (4) 6-2·8·9·10 형제 컬럼 확장  [우선순위 1]

**목적**: 동일 코드가 형제 측류 솔벤트 컬럼(6-2, 8, 9, 10차)에 그대로 도는지 검증 + startup 샘플 보강.

**선행 확인**:
1. 형제 컬럼 토폴로지 출처: `ff_column_config`엔 **C-6111(6-1)만** 있을 가능성 → 6-2/8/9/10은 **태그 네이밍 규칙으로 ROLES 유도**.
   - 6-1 규칙(끝자리 역할): feed=FICQ-**6101**, reflux=FICQ-6113, light(D)=6114, heavy(B)=6116, product(P)=6118, steam밸브=TICA-**6111A**.OP, steam유량=FIQ-6115, 민감단=TI-6111**C**, 진공=PICA-6111, DP=PI-6111B, 온도프로파일 TI-6111A/B/C/D.
   - 일반화: 차수·train 접두 `{P}1` (6-1=61, 6-2=62, 8=81, 9=91, 10=101). 즉 6-2=FICQ-6201/6213/6214/6216/6218, TICA-6211A 등.
2. **반드시 ptlist로 검증**: 각 형제의 실제 태그가 같은 끝자리 규칙인지 쿼리로 확인(8/9/10은 다를 수 있음).

**단계**:
1. `c6111_extract.py`의 `ROLES`를 **함수 `roles_for(prefix)`로 파라미터화**(예: prefix="62"→ FICQ-6201…). asset도 해당 차수(`/ASSETS/P6`는 6-1·6-2 공통, 8/9/10은 `/ASSETS/P8/9/10`).
2. ptlist로 각 컬럼 ROLES 자동검증(미해결 태그 경고). 끝자리 규칙이 다르면 컬럼별 매핑표 작성.
3. 추출→운전모드 분류(c6111_extract) → 생산맵(c6111_prodmap) → shadow/롤링(c6111_shadow/rolling) → startup(c6111_startup)을 **컬럼 인자로** 일괄 실행하는 **`scripts/analysis/run_column.py`** 래퍼 작성(기존 분석 스크립트와 같은 디렉토리·`BASE` 상수 정합 유지).
4. 컬럼별 산출: steam/feed비, 맵 R², shadow 일치율, startup 컷인 트리거.

**산출물**: 형제별 결과표(steam/feed, R², 컷인트리거) + 통합 startup 트리거(샘플↑로 reb-A/ΔT 변동성 재추정).

**검증기준**: 각 형제 생산맵 R²>0.95(운전점), shadow in-envelope 일치율>85%. startup 컷인 트리거가 6-1과 정합(같은 솔벤트 유형이면 유사 예상).

**주의**: 8/9/10은 컬럼 크기·운전조건 다를 수 있음 → steam/feed비·트리거가 6-1과 다르면 그게 정상(컬럼별 학습). 죽은 플랜트 3·4차 제외. 5차는 유형 미확인(§15.9).

**✅ 진행완료 (2026-06-05)**:
- 전 컬럼 추출·prodmap·shadow·rolling·startup·shutdown 완료 (run_column.py로 일괄).
- R²>0.95 달성: **6-1(0.993) ✓, 6-2(0.997) ✓**. 8(0.630)·9(0.886)는 미달(운전점 부족). 10(0.818) PROD 1.7%라 신뢰어려움.
- shadow in-envelope>85% 달성: **6-1(94%) ✓**. 6-2(79.7%, OOD 53%)·8(66.9%)·9(63.5%) 미달.
- 컷인 트리거: 6-1(reb-A 84.6±0.5℃)·6-2(82.3±2.6℃) 유사. 8(96.2℃)·9(83.0±7.7℃)·10(93.8±9.3℃)는 분산 큼.

---

## 작업 2 — ③ SHUTDOWN 절차  [우선순위 2]

**목적**: 안전 정지 절차 학습(startup의 역순). few-shot.

**선행**: `c6111_startup.py`를 템플릿으로. SHUTDOWN = **제품 컷오프(product >100→0 하강엣지)** 기준.

**단계**:
1. `detect_cutins`를 미러링한 `detect_cutoffs` 작성: product 하강엣지(>100→<50)이고 직후 steam도 내려감(shutdown 진입) 탐지.
2. 컷오프 정렬 후 역시퀀스 추출: 생산 → **피드 감소** → **제품 컷오프** → 전환류 복귀? → **스팀 차단** → **진공 해제** → 냉각.
3. 단계 타이밍 + 트리거: "언제 피드 줄이기 시작? 제품 컷오프 시 컬럼상태? 스팀 차단 순서?" 추출.
4. startup 에피소드 목록(02-15, 04-30, 05-02, 05-13 등)에 대응하는 shutdown이 직전에 있음(같은 에피소드의 앞부분) → 재활용.

**산출물**: SHUTDOWN 레시피(단계 시퀀스+타이밍+트리거) + 플롯(컷오프 정렬 중첩).

**검증기준**: 깨끗한 shutdown ≥3건에서 시퀀스 일관. 안전 관점(급격 차단 없이 순차 감소) 확인.

**주의**: shutdown은 안전 최우선 — 급격 진공해제/스팀차단 순서가 장비보호에 중요. 데이터의 순서·rate를 그대로 보존.

**✅ 진행완료 (2026-06-05)**:
- `c6111_shutdown.py` 생성 — `--data`/`--prefix` CLI로 형제 컬럼 호환.
- 전 컬럼 실행 완료. 6-1(4건)·6-2(5건)는 일관 시퀀스 확인, 9차(22건)는 3개 하위유형 식별.
- 셧다운 레시피 대표: **제품컷오프(trigger=reb-A 84.7℃) → steam 1분 차단 → 진공 4분 해제 → 냉각 140분+** (6-1 기준).
- 8·10차는 샘플 부족(1건·7건 산발)으로 일반화 어려움.

---

## 작업 3 — (2) operator-assist 패키징  [우선순위 3, 현장투입 1단계]

**목적**: ①의 예측기를 **"권장 OP + 신뢰도" 자문 출력**으로 패키징(write 안 함). 현장 신뢰구축 단계.

**선행**: `SteamPredictor`(c6111_shadow.py) + OOD 게이트 + 롤링 재학습 로직.

**단계**:
1. **예측 서비스화**(Python 먼저): `predict(live_tags) → {rec_OP, confidence(in/OOD), rec_steam_flow}`.
   - **입력 포맷**: `live_tags = {"feed": 534.2, "product": 318.5, "T_C": 84.7}` (FEATURES 키, 평활 전 원값). C# 포팅(작업4) `SteamAdvisor`도 **동일 키 계약** 유지.
   - 입력 평활(인과 trailing), 운전점맵 예측→밸브역특성→OP, envelope 체크.
   - 롤링 재학습 스케줄(일/주 단위, expanding 또는 trailing window).
2. **모드 인지**: 현재 운전모드(PROD/LINEOUT/STARTUP…) 분류(c6111_extract.classify_phases) → PROD에서만 ① 맵 조언, STARTUP이면 ② 레시피(컷인 게이트) 조언.
3. **출력 UI**: 기존 `...AdvisorService`/FF 자산(FeedforwardSupervisor, FfTrackingStore) 패턴으로 화면에 "권장 OP=X% (신뢰: 구간내/범위밖)" 표시 + 오퍼레이터 실조작 병행 로깅(비교).
4. shadow 로깅: 권장 vs 실제 OP 차이 누적 → 신뢰 리포트.

**산출물**: 자문 API/서비스 + 화면 + 권장-vs-실제 로그/리포트.

**검증기준**: 신뢰구간에서 권장 OP가 오퍼레이터와 ±2% 이내 90%+(§16.7 재현). OOD 시 "범위밖→수동" 정확 표시.

**주의**: **절대 write 금지**(advisory_only). OOD·비-PROD에선 조언 보류(폴백). hunting 공포 고려 — 조언도 gentle(작은 변화).

**✅ 진행완료 (2026-06-05)**:
- `c6111_operator_assist.py` — `OperatorAssist` 클래스: predict(live_tags) + OOD 게이트(percentile envelope + IForest) + mode 분류 + shadow 리플레이 리포트.
- `run_column.py`에 포함됨. 전 컬럼 shadow advisory 리포트 완료.
- **검증기준(±2% 90%+) 달성: 6-1(92.2%) ✅, 6-2(93.1%) ✅.** 8(84.3%)·9(80.8%)·10(61.9%) 미달.

---

## 작업 4 — (5) live C# shadow 포팅  [우선순위 4, 실플랜트 연결]

**목적**: 검증된 예측기를 C# 크롤러에 포팅, **플랜트6 live(gRPC)** 에 shadow 연결.

**선행**: 작업3 로직 확정. 플랜트6 HC900 통신 살아있음(live값 가공이라도 경로 테스트 가능, §0.2). `ff_column_config` C-6111 advisory_only=t 이미 설정.

**단계**:
1. **모델 산출물 export** — **(1안 권장) JSON 계수→C# 직접 계산**: 선형/룩업 계수(`steam≈0.73·feed+보정`), 밸브역특성 3차계수, envelope min/max를 JSON으로 export. 단순·투명·의존성 0. **(2안 fallback) ONNX**: 선형근사가 부족하고 GBM 정확도가 꼭 필요할 때만. **우선 1안으로 시작.**
2. **C# Infrastructure/Control에 예측기**: 기존 `FeedforwardSupervisor`/`FeedRampAdvisor` 옆에 `SteamAdvisor` 추가. gRPC 실시간 태그(피드 FICQ-6101, 제품 6118, T_C TI-6111C, reb-A, 진공 등) 읽어 권장 OP 산출.
3. **운전모드 분류 + OOD** C#에 포팅(임계 §16.3-2, envelope §16.7).
4. shadow 로깅: `FfTrackingStore`에 권장 vs 실제 OP 기록. 화면 노출(advisory).
5. 롤링 재학습: 주기적 Python 재학습→계수 갱신 파이프(또는 C#서 온라인 회귀).

**산출물**: C# `SteamAdvisor` 서비스(advisory) + live shadow 로그 + UI.

**검증기준**: live 태그로 권장 OP 산출·표시 동작. (live 데이터가 가공이라 정확도보다 **경로·안전(OOD폴백)·로깅** 검증 우선.)

**주의**: live값이 현재 시뮬/가공 → 정확도 평가는 field_hist 백테스트로, live는 통합·안전 검증용. write 금지. 실제 실데이터 확보 시 재검증.

**✅ 진행완료 (2026-06-05)**:
- `c6111_export_model.py` — 선형근사(1안) JSON export: linear 회귀계수·밸브역특성 3차계수·envelope min/max.
- C6-1 선형 R²=0.986(GBM 0.995 대비 99% 설명). C6-2 선형 R²=0.996(GBM 0.998).
- `SteamAdvisor.cs` (`src/Infrastructure/Control/`): `Predict(feed, product, tC)→SteamAdvisoryResult`, `ClassifyMode()`, `InEnvelope()`.
- `SteamAdvisorController.cs` (`src/Hc900Crawler/Controllers/`): `GET /api/steam/health`, `GET/POST /api/steam/predict`.
- `Program.cs` DI 등록 완료(`AddSingleton<SteamAdvisor>()`). `appsettings.json`에 `SteamAdvisor:ModelPath` 설정.

**⚠️ 선형근사 한계**: P8(0.659)·P9(0.161)·P10(0.202)는 선형 R² 낮음 → **2안(ONNX 또는 Python shadow 호출)** 으로 전환 검토 필요.

---

## 권장 순서
**작업1(형제확장) → 작업2(shutdown) → 작업3(operator-assist) → 작업4(live포팅)**.

**✅ 1·2·3 완료 (2026-06-05).** ~~작업4(live포팅)만 남음.~~

## 진행 현황

| 작업 | 상태 | 산출물 | 검증 |
|:----|:----:|:-------|:----:|
| 1. 형제 컬럼 확장 | ✅ 완료 | `run_column.py`, 각 `c{prefix}_*.png` | 6-1(0.993)✅ 6-2(0.997)✅ 8(0.630)❌ 9(0.886)❌ 10(0.818)❌ |
| 2. SHUTDOWN | ✅ 완료 | `c6111_shutdown.py`, 각 컬럼 shutdown 플롯 | 6-1(4건 일관)✅ 6-2(5건 일관)✅ 9(22건, 3유형 분류) |
| 3. Operator Assist | ✅ 완료 | `c6111_operator_assist.py`, 각 컬럼 advisory 리포트 | 6-1(92.2%)✅ 6-2(93.1%)✅ 8(84.3%)❌ 9(80.8%)❌ |
| 4. C# Live 포팅 | ✅ 완료 | `SteamAdvisor.cs`/`Controller.cs`, `c6111_export_model.py` | 빌드 0 errors ✅ API `/api/steam/predict` |

## 공통 참조
- 디코드/데이터: 플랜 §15. C-6111 토폴로지: §16.1. 방법론 교훈: §16.6(운전점), §16.7(OOD), §16.8(롤링).
- 코드 시작점: `scripts/analysis/c6111_extract.py`(tag_frame, ROLES, classify_phases), `c6111_shadow.py`(SteamPredictor).
