Files
HC900-Crawler/scripts/analysis/c6111_export_model.py
windpacer 7409fabc58 컬럼명칭 통일 C-xxxxx + SIGPIPE 대응 + SteamAdvisor/FF 개선
=== 컬럼명칭 통일 (c{prefix} → C-{prefix}11) ===
Python 분석스크립트: data pkl 경로  →
gen_temp_profiles: tempref 파일명  →
SteamAdvisorController: TagsFor() 숫자서픽스 → 풀컬럼키(C-6111), ToSuffix() 변환
steam.js: ST_TEMP_COLS ['61',...] → ['C-6111',...], selectbox defaultColumn
appsettings.json: Columns 키 c61/c62/... → C-6111/C-6211/..., DefaultColumn c6111→C-6111
run_column.py: 추출/분석시 col_key = f"C-{{prefix}}11"
C-{x}11_{model,tempref}.json: 신규 명칭 기준 기준프로파일/모델 7컬럼분

=== SteamAdvisor 수정 ===
SteamModel: [JsonPropertyName] 매핑(snake_case → PascalCase 역직렬화)
예외처리: LinearCoeffs.Count < 3 방어코드
steam.js: catch(_) {} → 에러메시지 표시, missing_tags 응답처리

=== Feedforward Controller 개선 ===
ff.js: 상승/하강 양방향 램프 confirm, 방향뱃지(↑↓), Normal 모드 표시
FeedforwardController: 업램프 단독제한 제거(양방향), tcReturnTcTarget/Band 노출

=== DB ===
Hc900DbContext: realtime_table_tagname_key 레거시 UNIQUE 제약/인덱스 DROP 로직
Hc900Controllers: ToDictionaryAsync → GroupBy 변환 (중복 tagname 대응)

=== SIGPIPE 대응 ===
gateway.cpp: signal(SIGPIPE, SIG_IGN) 메인스레드 설치
modbus_tcp.cpp: send() flags 0 → MSG_NOSIGNAL (EPIPE 복구)
sigpipe_ignore.c: LD_PRELOAD 우회 공유라이브러리
Hc900GatewayProcessService: LD_PRELOAD 환경변수 설정
2026-06-07 00:29:47 +09:00

81 lines
2.8 KiB
Python

"""
모델 JSON export → C# SteamAdvisor에서 로드.
선형근사(1안): GBM 대신 LinearRegression 계수 export.
steam = w0 + w1*feed + w2*product + w3*T_C
valve_inv(flow) = poly3 → OP
사용법:
python3 c6111_export_model.py --data c6111_data.pkl --prefix c6111
"""
import argparse
import json
import numpy as np
import pandas as pd
from sklearn.linear_model import LinearRegression
BASE = "/home/windpacer/projects/hc900_ax/scripts/analysis/"
FEATURES = ["feed", "product", "T_C"]
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--data", default=BASE + "C-6111_data.pkl")
parser.add_argument("--prefix", default="C-6111")
parser.add_argument("--output", help="JSON 출력 경로 (기본: scripts/analysis/{prefix}_model.json)")
args = parser.parse_args()
df = pd.read_pickle(args.data)
prod = df[df["mode"] == "PROD"].copy()
prod = prod[(prod["feed"] > 50) & (prod["steam_flow"] > 10) & (prod["steam_op"] > 1)]
prod = prod.dropna(subset=FEATURES + ["steam_op", "steam_flow"])
ops = (prod.set_index("dtat").resample("6h").median(numeric_only=True)
.dropna(subset=["steam_flow", "feed"]))
ops = ops[ops["feed"] > 50]
# 선형 모델
lr = LinearRegression()
lr.fit(ops[FEATURES].values, ops["steam_flow"].values)
r2 = lr.score(ops[FEATURES].values, ops["steam_flow"].values)
print(f"선형 steam_flow R² = {r2:.4f} (GBM 대비 비교용)")
# 밸브 역특성: steam_flow → steam_op (3차)
vp = np.polyfit(prod["steam_flow"], prod["steam_op"], 3)
# Envelope (1%, 99%)
lo = ops[FEATURES].quantile(0.01)
hi = ops[FEATURES].quantile(0.99)
# GBM feature importance (참고용)
try:
from sklearn.ensemble import GradientBoostingRegressor
gbm = GradientBoostingRegressor(n_estimators=200, max_depth=2,
learning_rate=0.05, random_state=0)
gbm.fit(ops[FEATURES].values, ops["steam_flow"].values)
gbm_r2 = gbm.score(ops[FEATURES].values, ops["steam_flow"].values)
except Exception:
gbm_r2 = None
model = {
"column": args.prefix,
"features": FEATURES,
"linear_coeffs": lr.coef_.tolist(),
"intercept": lr.intercept_,
"linear_r2": round(r2, 4),
"gbm_r2": round(gbm_r2, 4) if gbm_r2 else None,
"valve_poly": vp.tolist(),
"envelope_lo": {c: round(float(lo[c]), 1) for c in FEATURES},
"envelope_hi": {c: round(float(hi[c]), 1) for c in FEATURES},
"n_operating_points": len(ops),
"n_prod_rows": len(prod),
}
out = args.output or (BASE + f"{args.prefix}_model.json")
with open(out, "w") as f:
json.dump(model, f, indent=2)
print(f"\n모델 export: {out}")
print(json.dumps(model, indent=2))
if __name__ == "__main__":
main()