""" ② START-UP 절차 학습 (few-shot). 형제 컬럼 호환: --data, --prefix CLI 인자. """ import argparse 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(): parser = argparse.ArgumentParser() parser.add_argument("--data", default=BASE + "c6111_data.pkl") parser.add_argument("--prefix", default="c6111") args = parser.parse_args() df = pd.read_pickle(args.data).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 + f"{args.prefix}_startup.png", dpi=95) print(f"\n플롯 저장: {BASE}{args.prefix}_startup.png") if __name__ == "__main__": main()