Files
HC900-Crawler/docs/작업플랜-스팀컬럼명칭통일.md
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

13 KiB

작업플랜 — SteamAdvisor 컬럼명칭 통일 (2026-06-06)

목표

  1. appsettings.json에서 c6111/c61 중복 제거
  2. 컬럼키·파일prefix 모두 C-6111, C-6211, C-8111, C-9111, C-9211, C-10111, C-10211으로 통일
  3. 컬럼키 = 파일prefix → 매핑 헬퍼 불필요

통일 매핑

컬럼키 (UI/API) 파일 prefix 설명
C-6111 C-6111 6-1차
C-6211 C-6211 6-2차
C-8111 C-8111 8차
C-9111 C-9111 9-1차
C-9211 C-9211 9-2차
C-10111 C-10111 10-1차
C-10211 C-10211 10-2차

TagsFor numeric suffix: 컬럼키에서 "C-" prefix 제거 → "6111", "6211", "8111"


작업 0 — 파일 리네임 [선행 필수]

scripts/analysis/ 내 데이터 파일 일괄 리네임:

cd scripts/analysis

# Model files
mv c6111_model.json C-6111_model.json
mv c61_model.json C-6111_model.json.bak  # 중복, 제거
mv c62_model.json C-6211_model.json
mv c81_model.json C-8111_model.json
mv c91_model.json C-9111_model.json
mv c92_model.json C-9211_model.json
mv c101_model.json C-10111_model.json
mv c102_model.json C-10211_model.json

# Tempref files
mv c61_tempref.json C-6111_tempref.json
mv c62_tempref.json C-6211_tempref.json
mv c81_tempref.json C-8111_tempref.json
mv c91_tempref.json C-9111_tempref.json
mv c92_tempref.json C-9211_tempref.json
mv c101_tempref.json C-10111_tempref.json
mv c102_tempref.json C-10211_tempref.json

# Plotdata files
mv c6111_plotdata.json C-6111_plotdata.json
mv c61_plotdata.json C-6111_plotdata.json.bak  # 중복, 제거
mv c62_plotdata.json C-6211_plotdata.json
mv c81_plotdata.json C-8111_plotdata.json
mv c91_plotdata.json C-9111_plotdata.json
mv c92_plotdata.json C-9211_plotdata.json
mv c101_plotdata.json C-10111_plotdata.json
mv c102_plotdata.json C-10211_plotdata.json

동시 수정: 각 JSON 파일 내부의 "column" / "prefix" 필드 값도 C-6111 등으로 변경.

# JSON 내부 필드 수정 (python one-liner)
for f in C-*_model.json; do
  python3 -c "
import json, sys
d = json.load(open('$f'))
d['column'] = '$f'.replace('_model.json','')
json.dump(d, open('$f','w'), indent=2)
"
done

for f in C-*_plotdata.json; do
  python3 -c "
import json, sys
d = json.load(open('$f'))
d['prefix'] = '$f'.replace('_plotdata.json','')
json.dump(d, open('$f','w'), indent=2)
"
done

for f in C-*_tempref.json; do
  python3 -c "
import json, sys
d = json.load(open('$f'))
d['column'] = '$f'.replace('_tempref.json','')
json.dump(d, open('$f','w'), indent=2)
"
done

작업 1 — appsettings.json 수정

"SteamAdvisor": {
  "ModelPath": "/home/windpacer/projects/hc900_ax/scripts/analysis/C-6111_model.json",
  "PlotDataDir": "/home/windpacer/projects/hc900_ax/scripts/analysis",
  "ModelDir": "/home/windpacer/projects/hc900_ax/scripts/analysis",
  "DefaultColumn": "C-6111",
  "Columns": {
    "C-6111":  { "Feed": "FICQ-6101.PV", "Product": "FICQ-6118.PV", "TC": "TI-6111C",     "SteamOp": "TICA-6111A.OP", "SteamFlow": "FIQ-6115" },
    "C-6211":  { "Feed": "FICQ-6201.PV", "Product": "FICQ-6218.PV", "TC": "TI-6211C",     "SteamOp": "TICA-6211A.OP", "SteamFlow": "FIQ-6215" },
    "C-8111":  { "Feed": "FICQ-8101.PV",  "Product": "FICQ-8118.PV", "TC": "TI-8111C",     "SteamOp": "TICA-8111A.OP", "SteamFlow": "FIQ-8115" },
    "C-9111":  { "Feed": "FICQ-9101.PV",  "Product": "FICQ-9118.PV", "TC": "TI-9111C",     "SteamOp": "TICA-9111A.OP", "SteamFlow": "FIQ-9115" },
    "C-9211":  { "Feed": "FICQ-9201.PV",  "Product": "FICQ-9218.PV", "TC": "TI-9211C",     "SteamOp": "TICA-9211A.OP", "SteamFlow": "FIQ-9215" },
    "C-10111": { "Feed": "FICQ-10101.PV", "Product": "FICQ-10118.PV", "TC": "TI-10111C",   "SteamOp": "TICA-10111A.OP", "SteamFlow": "FIQ-10115" },
    "C-10211": { "Feed": "FICQ-10201.PV", "Product": "FICQ-10218.PV", "TC": "TI-10211C",   "SteamOp": "TICA-10211A.OP", "SteamFlow": "FIQ-10215" }
  }
}

작업 2 — SteamAdvisorController.cs 수정

2-1. 헬퍼 메서드 추가

// 컬럼키 "C-6111" → 파일prefix "C-6111" (동일)
// 컬럼키 "C-6111" → TagsFor numeric suffix "6111"
private static string ToSuffix(string col) => col.StartsWith("C-") ? col[2..] : col;

2-2. ListModels — 파일명에서 컬럼키 추출 (변경 불필요)

*_model.json 파일명의 _model.json 제거 → C-6111 반환. 컬럼키 = 파일prefix이므로 추가 변환 불필요.

// 변경 전
.Select(n => n!.Replace("_model", ""))  // "c6111"

// 변경 후 — 그대로 "C-6111" 반환
.Select(n => n!.Replace("_model", ""))

2-3. Backtest — 컬럼키 그대로 파일prefix 사용

// 변경 전
var path = Path.Combine(plotDir, $"{col}_plotdata.json");

// 변경 후 — 컬럼키 = 파일prefix
var path = Path.Combine(plotDir, $"{col}_plotdata.json");  // 동일!

2-4. TempProfile — 컬럼키 = 파일prefix, TagsFor는 numeric suffix

// 변경 전
var path = Path.Combine(dir, $"c{col}_tempref.json");  // col = "61"
var tagMap = TagsFor(col);

// 변경 후
var path = Path.Combine(dir, $"{col}_tempref.json");  // col = "C-6111"
var tagMap = TagsFor(ToSuffix(col));  // "6111"

2-5. TagsFor — numeric suffix 기반 태그명 생성

// 변경 전 — col = "61", "62", "81" 등
private static Dictionary<string, string> TagsFor(string p)
{
    var m = new Dictionary<string, string>
    {
        ["reb_temp"] = $"TICA-{p}11A.PV",
        ["T_B"] = $"TI-{p}11B.PV",
        ["T_C"] = $"TI-{p}11C.PV",
        ["T_D"] = $"TI-{p}11D.PV",
        ["vacuum"] = $"PICA-{p}11.PV",
    };
    switch (p) {
        case "51": m["T_C"] = "TI-5111B.PV"; break;
        case "81": m["reb_temp"] = "TICA-8111.PV"; m["vacuum"] = "PICA-8111A.PV"; break;
        // ...
    }
    return m;
}

// 변경 후 — p = "6111", "6211", "8111" 등
private static Dictionary<string, string> TagsFor(string p)
{
    var m = new Dictionary<string, string>
    {
        ["reb_temp"] = $"TICA-{p}A.PV",
        ["T_B"] = $"TI-{p}B.PV",
        ["T_C"] = $"TI-{p}C.PV",
        ["T_D"] = $"TI-{p}D.PV",
        ["vacuum"] = $"PICA-{p}.PV",
    };
    switch (p) {
        case "8111": m["reb_temp"] = "TICA-8111.PV"; m["vacuum"] = "PICA-8111A.PV"; break;
        case "9111": m["vacuum"] = "PICA-9111A.PV"; break;
        case "9211": m["vacuum"] = "PICA-9211A.PV"; break;
        case "10111": m["vacuum"] = "PICA-10111A.PV"; break;
        case "10211": m["vacuum"] = "PICA-10211A.PV"; break;
    }
    return m;
}

2-6. Live — 컬럼키 fallback 변경

// 변경 전
col ??= _config.GetValue<string>("SteamAdvisor:DefaultColumn") ?? "c6111";

// 변경 후
col ??= _config.GetValue<string>("SteamAdvisor:DefaultColumn") ?? "C-6111";

작업 3 — SteamAdvisor.cs 수정

// 변경 전
_modelPath = config.GetValue<string>("SteamAdvisor:ModelPath")
             ?? "/home/windpacer/projects/hc900_ax/scripts/analysis/c6111_model.json";

// 변경 후
_modelPath = config.GetValue<string>("SteamAdvisor:ModelPath")
             ?? "/home/windpacer/projects/hc900_ax/scripts/analysis/C-6111_model.json";

작업 4 — UI (steam.js) 수정

4-1. ST_TEMP_COLS — 컬럼키 사용

// 변경 전
const ST_TEMP_COLS = [['61','6-1차'],['62','6-2차'],['81','8차'],['91','9-1차'],['92','9-2차'],['101','10-1차'],['102','10-2차']];

// 변경 후
const ST_TEMP_COLS = [
  ['C-6111','6-1차'],['C-6211','6-2차'],['C-8111','8차'],
  ['C-9111','9-1차'],['C-9211','9-2차'],['C-10111','10-1차'],['C-10211','10-2차']
];

4-2. API 호출 — 컬럼키 그대로 전달

stLiveTick, stTempTick 모두 컬럼키를 API에 전달. 백엔드가 컬럼키를 직접 사용하므로 추가 변환 불필요.

4-3. stLoadColumns — 기본 선택 컬럼 강제 [★ 누락 A 해결]

알파벳순 정렬 시 C-10111C-6111보다 앞서 첫 옵션 자동 선택됨 → 데이터 없는 10차 → missing_tags → 불로딩 재현.

// 변경 전
[sel1, sel2].forEach(sel => {
  sel.innerHTML = cols.map(c => `<option value="${c}">${c}</option>`).join('');
});

// 변경 후 — DefaultColumn(C-6111)을 기본 선택으로 강제
const defaultCol = 'C-6111';  // 또는 d.defaultColumn에서 동적 획득
[sel1, sel2].forEach(sel => {
  sel.innerHTML = cols.map(c => `<option value="${c}" ${c===defaultCol?'selected':''}>${c}</option>`).join('');
});

4-3. stLoadColumns — 기본 선택 컬럼 강제 [★ 누락 A 해결]

알파벳순 정렬 시 C-10111C-6111보다 앞서 첫 옵션 자동 선택됨 → 데이터 없는 10차 → missing_tags → 불로딩 재현.

// 변경 전
[sel1, sel2].forEach(sel => {
  sel.innerHTML = cols.map(c => `<option value="${c}">${c}</option>`).join('');
});

// 변경 후 — DefaultColumn(C-6111)을 기본 선택으로 강제
const defaultCol = 'C-6111';  // 또는 d.defaultColumn에서 동적 획득
[sel1, sel2].forEach(sel => {
  sel.innerHTML = cols.map(c => `<option value="${c}" ${c===defaultCol?'selected':''}>${c}</option>`).join('');
});

작업 5 — Python 스크립트 출력 파일명 수정

Python 스크립트명 변경 불가 (- 포함 모듈 import 불가)이나, 출력 파일명C-6111 형식으로 변경.

수정 대상 (각 스크립트의 --prefix 기본값 + 출력 파일명):

스크립트 변경 항목
c6111_extract.py --data 기본값: c6111_data.pklC-6111_data.pkl
c6111_prodmap.py --prefix 기본값: c6111C-6111
c6111_shadow.py --prefix 기본값: c6111C-6111
c6111_rolling.py --prefix 기본값: c6111C-6111
c6111_startup.py --prefix 기본값: c6111C-6111
c6111_shutdown.py --prefix 기본값: c6111C-6111
c6111_operator_assist.py --prefix 기본값: c6111C-6111
c6111_export_model.py --prefix 기본값: c6111C-6111
gen_temp_profiles.py --prefix 기본값: c61C-6111
export_plotdata.py --prefix 기본값: c6111C-6111
run_column.py 스크립트 목록 + 데이터 파일명

주의: c6111_data.pklC-6111_data.pkl로 리네임 필요.


권장 순서

작업0 (파일 리네임) → 작업1 (appsettings) → 작업2 (Controller) → 작업3 (SteamAdvisor) → 작업4 (UI) → 작업5 (Python)

리스크

항목 수준 대응
파일 리네임 누락 ls scripts/analysis/로 확인
TagsFor switch case 누락 기존 6개 case → 5개 case로 변경 (51 제거)
Python 스크립트 --prefix 기본값 누락 스크립트 11개 확인

검증

  1. ls scripts/analysis/ — 모든 데이터 파일이 C-6111_ 등으로 시작
  2. dotnet build 성공
  3. /api/steam/models["C-6111", "C-6211", ...] 반환 (중복 없음)
  4. /api/steam/live?col=C-6111 → 정상 응답
  5. /api/steam/tempprofile/C-6111 → 정상 응답
  6. /api/steam/backtest/C-6111 → 정상 응답
  7. UI 컬럼 선택 → 차트 데이터 표시

재진단 보완 (2026-06-06) — 통일 작업플랜의 누락 2건

명칭 통일 방향은 타당하나, 이 작업만으로 원래 증상(라이브 불로딩)이 해결되지 않으며 Python 영향 범위가 과소평가됨.

🔴 누락 A — 통일해도 라이브 불로딩이 재현 (기본선택 강제 누락)

  • 이 문서 본진단의 증상 = "라이브 차트 불로딩", 실제 원인 = UI 기본 선택 컬럼이 알파벳순 첫 = 10차(재평가 §실제 원인).
  • 통일 후에도 OrderBy(x) 알파벳순 첫은 C-10111(1 < 6) → st-col 첫 옵션 자동선택 = 데이터 없는 10차 → missing_tags불로딩 그대로 재현.
  • DefaultColumn=C-6111(작업1)은 Live API의 col 누락 시 fallback일 뿐, UI select 기본선택과 별개(steam.js stLoadColumns가 첫 옵션 선택).
  • 추가 작업 필요(작업4에 포함): stLoadColumns에서 기본 선택을 DefaultColumn(C-6111)으로 강제 + missing_tags 사용자 표시. ★명칭 통일이 불로딩을 고친다는 착시 주의 — 별개 수정.

🟠 누락 B — Python c{prefix} 접두 패턴 (작업5 과소평가)

  • 분석 스크립트·gen_temp_profiles.py가 파일명을 f"c{prefix}_data.pkl"처럼 코드에 접두 c를 박아 생성. prefix=C-6111이면 "cC-6111_data.pkl"깨짐.
  • 작업5는 "--prefix 기본값 + 출력 파일명"만 명시 — 실제론 각 스크립트 내부 c{prefix}{prefix} 문자열 패턴을 모두 바꿔야 함.
  • gen_temp_profiles.build()의 6-1 fallback(if prefix=="61"c6111_data.pkl 경로 등도 함께 수정 대상.
  • 작업5 보강: "출력 파일명 변경" = 스크립트 내부 c{prefix} 접두 제거 + prefix 비교 리터럴("61","81" 등) 갱신.

🟡 경미 (인지됨)

  • 모듈명(c6111_extract.py, - import 불가로 변경 불가) vs 출력 prefix(C-6111) 영구 불일치 — 동작엔 무해하나 혼란 잔존. 헤더 주석으로 명시 권장.

종합

항목 통일 작업플랜 보완
명칭 혼재(c6111/c61 중복·3규약) 해결 타당
라이브 불로딩(원래 증상) 미해결 작업4에 기본선택=C-6111 강제 + missing_tags 표시 추가 필수
Python 파일명 ⚠️ 과소평가 작업5를 내부 c{prefix}{prefix} 패턴 변경으로 확장