신암정유 6-1차 측류 솔벤트 컬럼(C-6111) 실데이터로 오퍼레이터 모방 제어 분석: - 데이터 추출기 tag_frame() (field_hist WIDE 포맷 디코드) + 운전모드 분류 - ① 생산맵: 스팀유량=f(피드,제품,목표T_C) 운전점 GBM R²0.99 (steam/feed≈0.73) - shadow 백테스트: in-envelope 오퍼레이터 OP 94% 모방, OOD 게이트→폴백 - 롤링 재학습: 새 로드레짐 적응 (5월 OP MAE 3.9→1.2%) - 캠페인내 트림: 컬럼 자기제어, 피드백 미미 → 전향 맵이 제어 지배 - ② START-UP 절차: 레시피 + 제품컷인 트리거(reb-A 84.5℃, ΔT 2℃, 조건기반) 문서: 설계·진행 플랜 + 남은 작업(형제확장·shutdown·assist·live포팅) 작업지시서. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
110 lines
4.8 KiB
Python
110 lines
4.8 KiB
Python
"""
|
|
C-6111 ② START-UP 절차 학습 (플랜 §16.4 ②, few-shot).
|
|
|
|
startup 에피소드를 탐지→스팀투입 시점(t0)에 정렬→중첩, 절차를 해석가능 레시피로 추출:
|
|
단계 시퀀스(진공→스팀/승온→전환류 라인아웃→제품컷인→로드램프→생산),
|
|
각 단계 타이밍, 그리고 ★핵심 결정 "제품 컷인" 시점의 컬럼 상태(트리거)★.
|
|
블랙박스 정책 아님 — 안전·설명가능 우선.
|
|
"""
|
|
import numpy as np
|
|
import pandas as pd
|
|
import matplotlib
|
|
matplotlib.use("Agg")
|
|
import matplotlib.pyplot as plt
|
|
|
|
BASE = "/home/windpacer/projects/hc900_ax/scripts/analysis/"
|
|
|
|
|
|
def detect_cutins(df):
|
|
"""★제품 컷인★ 이벤트: product 0→>100 상향, 직전 30분 라인아웃(product<50)이고 hot(reb>75)."""
|
|
prod = df["product"].values
|
|
reb = df["reb_temp"].values
|
|
outs = []
|
|
i = 60
|
|
n = len(df)
|
|
while i < n:
|
|
if prod[i] > 100 and prod[i-1] <= 100:
|
|
pre = prod[max(0, i-60):i] # 직전 30분
|
|
if np.nanmedian(pre) < 50 and reb[i] > 75: # 라인아웃(제품off)+hot
|
|
outs.append(i)
|
|
i += 720
|
|
continue
|
|
i += 1
|
|
return outs
|
|
|
|
|
|
def milestones(df, ci):
|
|
"""제품 컷인 인덱스 ci 기준 절차 추출."""
|
|
tc = df["dtat"].iloc[ci]
|
|
# 역방향: 스팀투입(steam_op>10 연속 시작) — 컷인 직전 steam off→on
|
|
back = df.iloc[max(0, ci-1200):ci]
|
|
off = back[back["steam_op"] <= 10]
|
|
i_steam = off.index[-1] + 1 if len(off) else back.index[0]
|
|
# 리플럭스 확립(스팀투입 이후 reflux>100 첫)
|
|
aft = df.iloc[i_steam:ci]
|
|
r_on = aft[aft["reflux"] > 100]
|
|
i_refl = r_on.index[0] if len(r_on) else None
|
|
# 풀로드(컷인 이후 feed>250 첫)
|
|
fwd = df.iloc[ci:ci+1200]
|
|
f_on = fwd[fwd["feed"] > 250]
|
|
i_full = f_on.index[0] if len(f_on) else None
|
|
|
|
def mins(i):
|
|
return None if i is None else (df["dtat"].iloc[i]-tc).total_seconds()/60
|
|
r = df.iloc[ci]
|
|
return dict(cutin_time=tc,
|
|
steam_to_cutin=-mins(i_steam),
|
|
reflux_to_cutin=(-mins(i_refl) if i_refl is not None else None),
|
|
cutin_to_full=mins(i_full),
|
|
cutin_rebA=r["reb_temp"], cutin_TC=r["T_C"], cutin_TD=r["T_D"],
|
|
cutin_dT_AD=r["reb_temp"]-r["T_D"])
|
|
|
|
|
|
def main():
|
|
df = pd.read_pickle(BASE + "c6111_data.pkl").sort_values("dtat").reset_index(drop=True)
|
|
cutins = detect_cutins(df)
|
|
print(f"탐지된 ★제품 컷인★(진짜 startup) 이벤트: {len(cutins)}개")
|
|
|
|
rows, windows = [], []
|
|
for ci in cutins:
|
|
w = df.iloc[max(0, ci-360):min(len(df), ci+360)].copy() # 컷인 ±3h
|
|
w["rel_min"] = (w["dtat"] - df["dtat"].iloc[ci]).dt.total_seconds()/60
|
|
windows.append(w)
|
|
rows.append(milestones(df, ci))
|
|
M = pd.DataFrame(rows)
|
|
pd.set_option("display.width", 220)
|
|
print("\n=== 제품컷인 기준 절차(분) + 컷인 시점 컬럼상태 ===")
|
|
cols = ["cutin_time", "steam_to_cutin", "reflux_to_cutin", "cutin_to_full",
|
|
"cutin_rebA", "cutin_TC", "cutin_dT_AD"]
|
|
show = M[cols].copy()
|
|
show["cutin_time"] = show["cutin_time"].dt.strftime("%m-%d %H:%M")
|
|
print(show.round(1).to_string(index=False))
|
|
print("\n=== 절차 레시피(중앙값) ===")
|
|
print(f" 스팀투입→제품컷인(전환류 라인아웃 길이): {M.steam_to_cutin.median():.0f}분")
|
|
print(f" 리플럭스확립→제품컷인 : {M.reflux_to_cutin.median():.0f}분")
|
|
print(f" 제품컷인→풀로드 : {M.cutin_to_full.median():.0f}분")
|
|
print(f" ★제품컷인 트리거(컬럼상태): reb-A={M.cutin_rebA.median():.1f}±{M.cutin_rebA.std():.1f}℃, "
|
|
f"T_C={M.cutin_TC.median():.1f}±{M.cutin_TC.std():.2f}℃, ΔT(A-D)={M.cutin_dT_AD.median():.1f}℃")
|
|
|
|
fig, ax = plt.subplots(4, 1, figsize=(13, 11), sharex=True)
|
|
for k, w in enumerate(windows):
|
|
c = plt.cm.tab10(k)
|
|
ax[0].plot(w.rel_min, w.reb_temp, color=c, lw=.9, label=f"ep{k+1} {w.dtat.iloc[len(w)//2]:%m-%d}")
|
|
ax[0].plot(w.rel_min, w["T_D"], color=c, lw=.6, ls=":")
|
|
ax[1].plot(w.rel_min, w.steam_flow, color=c, lw=.9)
|
|
ax[2].plot(w.rel_min, w.reflux, color=c, lw=.9)
|
|
ax[2].plot(w.rel_min, w["product"], color=c, lw=.9, ls="--")
|
|
ax[3].plot(w.rel_min, w.feed, color=c, lw=.9)
|
|
ax[0].set_ylabel("reb_temp/T_D(:)"); ax[0].legend(fontsize=7)
|
|
ax[0].set_title("STARTUP aligned at PRODUCT CUT-IN (rel=0)")
|
|
ax[1].set_ylabel("steam flow"); ax[2].set_ylabel("reflux/product(--)")
|
|
ax[3].set_ylabel("feed"); ax[3].set_xlabel("minutes from product cut-in")
|
|
for a in ax:
|
|
a.axvline(0, c="k", lw=.5)
|
|
fig.tight_layout(); fig.savefig(BASE + "c6111_startup.png", dpi=95)
|
|
print(f"\n플롯 저장: {BASE}c6111_startup.png")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|