Files
HC900-Crawler/scripts/analysis/run_column.py
windpacer b45f0e2481 fix: 누락 솔벤트 컬럼 추가 (5차·9-2·10-2) + 5차 T_C 센서 부재 처리
run_column.py 실행목록에 빠졌던 컬럼 보완 — 전체 8개 측류 솔벤트 컬럼 커버:
- 5차(51/P5) 신규 추가: 완전 누락이었음. 민감단 TI-5111C 센서 부재(A/B/D만)
  → COLUMN_EXCEPTIONS["51"]에서 T_C를 TI-5111B로 대체(사용자 확정).
- 9-2(92), 10-2(102) 추가: COLUMN_EXCEPTIONS엔 있었으나 COLUMNS 실행목록 누락.

검증: 8개 컬럼 핵심 FEATURES 전부 해석 OK, 5차·9-2 추출 스모크 정상(336519행).
(플랜트8은 단일 train=81, 8-2 없음 확인.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 20:45:04 +09:00

177 lines
6.1 KiB
Python

"""
형제 컬럼 확장(작업1) 일괄 실행 래퍼.
사용법:
python3 run_column.py --prefix 62 # 6-2차 단독
python3 run_column.py --prefix 81 --asset /ASSETS/P8
python3 run_column.py --all # 모든 형제 컬럼
"""
import argparse
import subprocess
import sys
import os
import psycopg
import pandas as pd
BASE = os.path.dirname(os.path.abspath(__file__))
COLUMNS = [
("51", "/ASSETS/P5", "C-5111 (5차)"), # 측류 솔벤트, T_C 센서 부재(대체)
("61", "/ASSETS/P6", "C-6111 (6-1차)"),
("62", "/ASSETS/P6", "C-6211 (6-2차)"),
("81", "/ASSETS/P8", "C-8111 (8차, 단일 train)"),
("91", "/ASSETS/P9", "C-9111 (9-1차)"),
("92", "/ASSETS/P9", "C-9211 (9-2차)"),
("101", "/ASSETS/P10", "C-10111 (10-1차)"),
("102", "/ASSETS/P10", "C-10211 (10-2차)"),
]
PREFIX_ASSET = {p: a for p, a, _ in COLUMNS}
DSN = "host=localhost port=5432 dbname=field_hist user=postgres password=postgres"
PY = sys.executable
def extract(prefix, asset):
"""추출 + 운전모드 분류. c{prefix}_data.pkl 저장."""
from c6111_extract import roles_for, tag_frame, classify_phases
with psycopg.connect(DSN) as conn:
roles = roles_for(prefix, asset)
print(f"\n ROLES ({len(roles)}):")
for k, v in roles.items():
print(f" {k:15s} -> {v}")
df = tag_frame(conn, roles, asset)
df["mode"] = classify_phases(df)
out = os.path.join(BASE, f"c{prefix}_data.pkl")
df.to_pickle(out)
print(f"\n=== {prefix} ({asset}) ===")
print(f" 행수={len(df)} 기간={df.dtat.min()} ~ {df.dtat.max()}")
vc = df["mode"].value_counts()
for m, n in vc.items():
print(f" {m:9s} {n:7d} {100*n/len(df):5.1f}% ≈ {n*30/3600:.1f}h")
print(f" 저장: {out}")
return out
def run_analysis(script, prefix):
"""분석 스크립트 1개 실행 (subprocess)."""
data = os.path.join(BASE, f"c{prefix}_data.pkl")
cmd = [PY, os.path.join(BASE, script), "--data", data, "--prefix", f"c{prefix}"]
print(f"\n>>> {' '.join(cmd)}")
r = subprocess.run(cmd)
return r.returncode
def run_column(prefix, asset, label):
"""컬럼 1개 전체 파이프라인."""
print(f"\n{'='*60}")
print(f" {label} (prefix={prefix}, asset={asset})")
print(f"{'='*60}")
extract(prefix, asset)
for script in ["c6111_prodmap.py", "c6111_shadow.py", "c6111_rolling.py", "c6111_startup.py", "c6111_shutdown.py", "c6111_operator_assist.py", "c6111_export_model.py"]:
rc = run_analysis(script, prefix)
if rc != 0:
print(f" [WARN] {script} → exit {rc}")
def compare():
"""모든 컬럼 결과 취합 → 비교표 (prodmap + shadow + startup)."""
import numpy as np
rows = []
for prefix, asset, label in COLUMNS:
pkl = os.path.join(BASE, f"c{prefix}_data.pkl")
# 6-1 legacy: c6111_data.pkl (not c61_data.pkl)
if prefix == "61" and not os.path.exists(pkl):
alt = os.path.join(BASE, "c6111_data.pkl")
if os.path.exists(alt):
pkl = alt
if not os.path.exists(pkl):
print(f" [skip] {label}: {pkl} 없음")
continue
df = pd.read_pickle(pkl)
prod = df[df["mode"] == "PROD"]
steam_feed = prod["steam_flow"].median() / prod["feed"].median() if len(prod) else float("nan")
total_h = len(df) * 30 / 3600
prod_h = len(prod) * 30 / 3600
# 컷인 탐지 (startup.py detect_cutins 로직 인라인)
prod_arr = df["product"].values
reb_arr = df["reb_temp"].values
dtat_vals = df["dtat"].values
cutins = []
i = 60
n = len(df)
while i < n:
if prod_arr[i] > 100 and prod_arr[i-1] <= 100:
pre = prod_arr[max(0, i-60):i]
if np.nanmedian(pre) < 50 and reb_arr[i] > 75:
cutins.append(i)
i += 720
continue
i += 1
row = {"컬럼": label,
"기간": f"{df['dtat'].min():%m-%d}~{df['dtat'].max():%m-%d}",
"전체(h)": f"{total_h:.0f}",
"PROD%": f"{100*len(prod)/len(df):.1f}",
"생산(h)": f"{prod_h:.0f}",
"steam/feed": f"{steam_feed:.3f}",
"컷인": str(len(cutins))}
if cutins:
cutin_data = []
for ci in cutins:
cutin_data.append({"reb": df.loc[ci, "reb_temp"],
"tc": df.loc[ci, "T_C"],
"dT": df.loc[ci, "reb_temp"] - df.loc[ci, "T_D"]})
cdf = pd.DataFrame(cutin_data)
row["컷인_reb-A"] = f"{cdf['reb'].median():.1f}±{cdf['reb'].std():.1f}"
row["컷인_dT_AD"] = f"{cdf['dT'].median():.1f}±{cdf['dT'].std():.1f}"
else:
row["컷인_reb-A"] = ""
row["컷인_dT_AD"] = ""
rows.append(row)
pd.set_option("display.width", 300)
pd.set_option("display.max_columns", 20)
print("\n\n" + "="*120)
print(" 형제 컬럼 비교표")
print("="*120)
tbl = pd.DataFrame(rows).set_index("컬럼")
print(tbl.to_string())
def main():
parser = argparse.ArgumentParser(description="형제 컬럼 확장 일괄 실행")
parser.add_argument("--prefix", help="컬럼 prefix (61, 62, 81, 91, 101)")
parser.add_argument("--asset", help="asset 경로 (예: /ASSETS/P6)")
parser.add_argument("--all", action="store_true", help="모든 형제 컬럼 실행")
parser.add_argument("--compare", action="store_true", help="기존 pkl로 비교표만 출력")
args = parser.parse_args()
if args.compare:
compare()
elif args.all:
for prefix, asset, label in COLUMNS:
run_column(prefix, asset, label)
compare()
elif args.prefix:
asset = args.asset or PREFIX_ASSET.get(args.prefix, f"/ASSETS/P{args.prefix[0]}")
label = f"C-{args.prefix}11"
for p, a, l in COLUMNS:
if p == args.prefix:
label = l
break
run_column(args.prefix, asset, label)
else:
parser.print_help()
if __name__ == "__main__":
main()