- daily_report.py: 지정 기간 일별 운전값(피드·스팀·비율·온도) + 생산맵 예측품질 (predOP_MAE·within2%·OOD) 표. --prefix/--asset/--start/--end/--train-end. 6-2차 4~5월 검증: 신뢰구간 OP MAE 0.89%, 고부하(OOD) 외삽으로 최대 -10%. - run_column.py: 10차(101/102)는 본 dump 창 대부분 시운전(PROD 2~6%) 주석. 2026-06 생산 진입 → 데이터 누적/최신 dump 재반입 시 정상 분석 대상. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
79 lines
3.4 KiB
Python
79 lines
3.4 KiB
Python
"""
|
|
컬럼 일별 리포트 — 지정 기간 일별 운전값 + 생산맵 예측품질(shadow).
|
|
|
|
사용: python3 daily_report.py --prefix 62 --asset /ASSETS/P6 \
|
|
--start 2026-04-01 --end 2026-06-01 --train-end 2026-04-01
|
|
|
|
train-end 이전 PROD로 생산맵 학습 → start~end 일별로 예측 OP vs 실제 비교(전진 shadow).
|
|
"""
|
|
import argparse
|
|
import numpy as np
|
|
import pandas as pd
|
|
import psycopg
|
|
from c6111_extract import roles_for, tag_frame, classify_phases, DSN
|
|
from c6111_shadow import SteamPredictor, FEATURES, SMOOTH
|
|
from sklearn.metrics import mean_absolute_error
|
|
|
|
|
|
def main():
|
|
ap = argparse.ArgumentParser()
|
|
ap.add_argument("--prefix", default="62")
|
|
ap.add_argument("--asset", default="/ASSETS/P6")
|
|
ap.add_argument("--start", default="2026-04-01")
|
|
ap.add_argument("--end", default="2026-06-01")
|
|
ap.add_argument("--train-end", default="2026-04-01")
|
|
a = ap.parse_args()
|
|
|
|
with psycopg.connect(DSN) as conn:
|
|
df = tag_frame(conn, roles_for(a.prefix, a.asset), a.asset)
|
|
df["mode"] = classify_phases(df)
|
|
for c in FEATURES:
|
|
df[c + "_s"] = df[c].rolling(SMOOTH, min_periods=1).median()
|
|
|
|
prod = df[df["mode"] == "PROD"].copy()
|
|
prod = prod[(prod["feed"] > 50) & (prod["steam_flow"] > 10) & (prod["steam_op"] > 1)]
|
|
te = pd.Timestamp(a.train_end)
|
|
train = prod[prod["dtat"] < te]
|
|
print(f"전체 {df.dtat.min()}~{df.dtat.max()}, PROD {len(prod)}행. "
|
|
f"학습(<{a.train_end}) {len(train)}행")
|
|
|
|
pred = SteamPredictor().fit(train) if len(train) > 500 else None
|
|
if pred:
|
|
lo, hi = train[FEATURES].quantile(0.01), train[FEATURES].quantile(0.99)
|
|
|
|
s, e = pd.Timestamp(a.start), pd.Timestamp(a.end)
|
|
win = df[(df["dtat"] >= s) & (df["dtat"] < e)]
|
|
rows = []
|
|
for day, g in win.groupby(win["dtat"].dt.date):
|
|
gp = g[(g["mode"] == "PROD") & (g["feed"] > 50) & (g["steam_flow"] > 10) & (g["steam_op"] > 1)]
|
|
r = dict(date=day, n=len(g),
|
|
PROD=round(100*(g["mode"] == "PROD").mean()),
|
|
feed=gp["feed"].median(), steam=gp["steam_flow"].median(),
|
|
ratio=round((gp["steam_flow"]/gp["feed"]).median(), 3) if len(gp) else np.nan,
|
|
T_C=round(gp["T_C"].median(), 2) if len(gp) else np.nan,
|
|
OP=round(gp["steam_op"].median(), 1) if len(gp) else np.nan)
|
|
if pred and len(gp) > 5:
|
|
Xs = gp[[c + "_s" for c in FEATURES]].values
|
|
po = pred.flow_to_op(pred.predict_flow(Xs))
|
|
ao = gp["steam_op"].values
|
|
env = ((gp[FEATURES] >= lo) & (gp[FEATURES] <= hi)).all(axis=1)
|
|
r["predOP_MAE"] = round(mean_absolute_error(ao, po), 2)
|
|
r["within2%"] = round(100*np.mean(np.abs(po-ao) <= 2))
|
|
r["OOD%"] = round(100*(~env).mean())
|
|
rows.append(r)
|
|
R = pd.DataFrame(rows)
|
|
pd.set_option("display.width", 200)
|
|
print(f"\n=== {a.prefix} 일별 리포트 ({a.start}~{a.end}) ===")
|
|
R["date"] = R["date"].astype(str)
|
|
print(R.round(1).to_string(index=False))
|
|
print("\n=== 요약 ===")
|
|
print(f" feed 중앙 {R.feed.median():.0f}, steam 중앙 {R.steam.median():.0f}, "
|
|
f"steam/feed비 중앙 {R.ratio.median():.3f}")
|
|
if "predOP_MAE" in R:
|
|
print(f" 예측 OP MAE 중앙 {R.predOP_MAE.median():.2f}%, "
|
|
f"within2% 중앙 {R['within2%'].median():.0f}%, OOD 중앙 {R['OOD%'].median():.0f}%")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|