feat: 일별 리포트 스크립트 + 10차 시운전 caveat

- 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>
This commit is contained in:
windpacer
2026-06-05 21:47:37 +09:00
parent 3a7c9d60c3
commit 23a9202b91
2 changed files with 83 additions and 2 deletions

View File

@@ -0,0 +1,78 @@
"""
컬럼 일별 리포트 — 지정 기간 일별 운전값 + 생산맵 예측품질(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()

View File

@@ -22,9 +22,12 @@ COLUMNS = [
("81", "/ASSETS/P8", "C-8111 (8차, 단일 train)"), ("81", "/ASSETS/P8", "C-8111 (8차, 단일 train)"),
("91", "/ASSETS/P9", "C-9111 (9-1차)"), ("91", "/ASSETS/P9", "C-9111 (9-1차)"),
("92", "/ASSETS/P9", "C-9211 (9-2차)"), ("92", "/ASSETS/P9", "C-9211 (9-2차)"),
("101", "/ASSETS/P10", "C-10111 (10-1차)"), ("101", "/ASSETS/P10", "C-10111 (10-1차) ※본 dump엔 시운전기간 多, 생산샘플 적음"),
("102", "/ASSETS/P10", "C-10211 (10-2차)"), ("102", "/ASSETS/P10", "C-10211 (10-2차) ※첫생산 5/26~, 이후 데이터 유효"),
] ]
# ※ 10차는 본 dump 창(2026-02~06) 대부분이 시운전이라 PROD 샘플이 적음(10-1 2%, 10-2 5/26~).
# 단 2026-06 현재 정상 생산 진입 — 데이터 누적되면(또는 최신 dump 재반입) 정상 분석 대상.
# 현재는 생산구간만 추려 쓰되 샘플 부족에 주의(맵 R²·트리거 변동성 클 수 있음).
PREFIX_ASSET = {p: a for p, a, _ in COLUMNS} PREFIX_ASSET = {p: a for p, a, _ in COLUMNS}