Files
HC900-Crawler/docs/진단보고서-작업1-4.md
windpacer 4306f76ddb feat: 형제 컬럼(6-2~10차) 분석 + SHUTDOWN + operator-assist + C# SteamAdvisor 포팅
- c6111_extract: roles_for() 동적 생성, COLUMN_EXCEPTIONS per-prefix
- c6111_prodmap/shadow/startup/rolling: --data/--prefix CLI 인자 지원
- run_column.py: 5개 컬럼 전 파이프라인 실행 래퍼
- c6111_shutdown.py: detect_cutoffs + shutdown_milestones (lookback 1200)
- c6111_operator_assist.py: OOD 게이트 + shadow 리플레이
- c6111_export_model.py: 선형근사 JSON export
- SteamAdvisor.cs: Predict+ClassifyMode+InEnvelope (NaN guard, Ood fix)
- SteamAdvisorController: GET/POST /api/steam/predict
- appsettings.json/Program.cs: DI 등록
- docs: 작업지시서 현황 갱신, 진단보고서 작성 (3 MED/8 LOW, 100% 정확도)
2026-06-05 19:46:57 +09:00

11 KiB

진단 보고서 — 학습형 제어 (작업 1~4)

진단 일시: 2026-06-05
진단 규칙: diagnosis-checklist.md 8단계
진단 범위: scripts/analysis/ (run_column, shutdown, operator_assist, export_model) + src/Infrastructure/Control/SteamAdvisor.cs + src/Hc900Crawler/Controllers/SteamAdvisorController.cs + Program.cs 수정사항
이전 보고서: docs/진단보고서-학습형제어-1차.md (작업지시서 문서 진단, c6111_*.py 기존 분석기)


🔴 HIGH — 없음


🟠 MED

M1. SteamAdvisor.Predict() 반환값 Ood/InEnv 하드코딩 오류

문제: PROD 모드 분기에서 inEnv=false여도 반환 SteamAdvisoryResultOod=false, InEnv=true로 항상 고정. 클라이언트가 confidence가 아닌 Ood/InEnv로 판단하면 범위밖 입력을 구간내로 오인. message("⚠ 범위밖 입력")와 confidence("LOW_OOD")는 정상이나, 구조화 필드가 실제와 불일치.
근거: src/Infrastructure/Control/SteamAdvisor.cs:107Ood = false, InEnv = true 하드코딩. 의도된 값은 Ood = !inEnv, InEnv = inEnv.
영향: confidence는 정상이나 ood:false, inEnv:true로 응답받은 API 클라이언트가 advisory를 수용할 위험. 또는 confidence만 보고 무시하더라도 데이터 파이프라인(로깅·추세)에 잘못된 envelope 정보 기록.
수정: 1줄 변경.

// before (SteamAdvisor.cs:107)
Ood = false, InEnv = true,
// after
Ood = !inEnv, InEnv = inEnv,

M2. c6111_shutdown.py 피드감소 시작점 탐색창 부족

문제: shutdown_milestones()의 feed 감소 시작 탐색이 600행(5시간) lookback으로 제한. P9/P10처럼 선행 피드감소 시간이 긴 shutdown 이벤트에서 탐색창 끝에 도달해도 feed 감소 미발견 → feed_to_cutoff=299.5분(최대값) 기록.
근거: scripts/analysis/c6111_shutdown.py:39for j in range(co, max(0, co - 600), -1). 600행 = 5시간(30s 주기 기준). P9 5/22건이 299.5분 기록.
영향: 해당 건의 feed_to_cutoff가 실제보다 짧게 추정되어 셧다운 레시피의 "피드감소→컷오프" 중앙값 왜곡(현재 55분 → 실제는 더 길 수 있음).
수정: lookback을 co - 1200(10시간)으로 확장. 또는 feed 안정화 판정(rolling std < threshold)을 동적 종료 조건으로 추가.

# c6111_shutdown.py:39
for j in range(co, max(0, co - 1200), -1):

M3. SteamAdvisor.Predict() NaN 입력 무방비

문제: feed·product·tC 인자가 double.NaN이면 선형 계산 NaNPolyVal(NaN)NaNMath.Clamp(NaN, 0, 100) = NaN → HTTP 응답 rec_OP: NaN. 라이브 데이터에서 HC900 통신 끊김 등으로 quality 불량 시 NaN이 유입되면 API가 NaN을 그대로 반환.
근거: src/Infrastructure/Control/SteamAdvisor.cs:65-108 — Predict() 입구에 NaN 사전 차단 없음. double.IsNaN() 검증 생략.
영향: GET /api/steam/predict?feed=NaN&product=300&tC=85{"rec_OP": NaN, ...}. JSON에 NaN은 유효하지 않아 HTTP 500 또는 클라이언트 파싱 실패 유발.
수정: Predict() 선두에 NaN 가드.

if (double.IsNaN(feed) || double.IsNaN(product) || double.IsNaN(tC))
    return new SteamAdvisoryResult {
        Message = "입력값에 NaN 포함", Confidence = "N/A",
        Mode = "INVALID", Feed = feed, Product = product, TC = tC };

🟡 LOW

L4. Python BASE 경로 하드코딩 7개 파일

문제: c6111_shutdown.py·operator_assist.py·export_model.py·prodmap.py·shadow.py·startup.py·rolling.py 7개 파일에 BASE = "/home/windpacer/projects/hc900_ax/scripts/analysis/" 절대경로 하드코딩. run_column.py만 동적(os.path.dirname(os.path.abspath(__file__))) 사용.
근거: 각 파일의 BASE 상수 선언부.
영향: 저장소 이동 시 plot 저장 실패. 단, --data/--prefix CLI 인자로 runtime override 가능.
수정: run_column.py와 통일하여 os.path.dirname(os.path.abspath(__file__))로 변경.

L5. SteamAdvisor.PolyVal 계수 부족 시 pass-through (무경고)

문제: ValvePoly 계수가 4개 미만이면 coeffs[0]*x^3 + ... 계산 시 IndexOutOfRange 전에 if (coeffs.Count < 4) return x;로 빠짐 → 로그 없이 입력값을 OP로 반환. export 불량 발견 불가.
근거: SteamAdvisor.cs:140-141.
영향: valve poly가 3차(4계수)가 아닌 모델을 로드해도 OP 계산 오류를 감지 불가.
수정: 경고 로그 추가. _logger.LogWarning(...) 후 pass-through.

L6. 컨트롤러 입력 검증 속성 누락

문제: SteamAdvisorController[FromQuery] double feed 등에 [Required]·[Range] 속성 없음. POST /api/steam/predict body SteamPredictBody에도 검증 속성 없음. 인자 생략 시 0.0 자동 전달.
근거: src/Hc900Crawler/Controllers/SteamAdvisorController.cs:23,33,41.
영향: 생략된 인자가 0으로 전달되어 비현실적인 권장 OP 산출.
수정: [FromQuery] 파라미터에 [Required] 추가, PredictRequest[Range(0, double.MaxValue)] 속성.

L7. run_column.compare() cutin 탐지 로직 중복

문제: compare() 함수 내에 startup.py/detect_cutins와 동일한 product 상향엣지 탐지 로직이 인라인 복사되어 있음. startup.pydetect_cutins 로직이 변경되면 compare()의 결과와 불일치 발생.
근거: run_column.py:103-112 vs c6111_startup.py:16-31. 96% 동일 코드.
영향: startup 탐지 알고리즘 개선 시 비교표가 업데이트되지 않아 검증 결과 혼선.
수정: compare()에서 from c6111_startup import detect_cutins 호출로 대체.

L8. c6111_export_model.py R² on training set

문제: LinearRegression.score()GradientBoostingRegressor.score()가 모두 fit()에 사용한 동일 데이터셋으로 평가. train/test 분할 없어 R²가 과대추정됨. export된 JSON의 linear_r2gbm_r2가 실제 일반화 성능보다 높게 표시.
근거: c6111_export_model.py:39,55. ops[FEATURES]를 fit과 score에 동일 사용.
영향: C# 측에서 SteamModel.LinearR2를 신뢰도 지표로 사용 시 과신. 현행 C# 코드는 R²를 비즈니스 로직에 사용하지 않아 실질 영향 없음.
수정: train_test_split으로 분할 후 held-out R²도 export.

L9. run_column.py DSN 평문 하드코딩

문제: PostgreSQL 연결문자열 "host=localhost port=5432 dbname=field_hist user=postgres password=postgres"가 소스코드에 평문 하드코딩. 버전관리 대상 파일.
근거: run_column.py:28.
영향: Dev DB 전용(로컬 PG16 컨테이너), 실제 민감정보 아님. 단, 리포지토리가 외부에 공개될 때 내부 DB 구조 노출.
수정: 환경변수 PG_DSN 또는 .env 파일에서 읽도록 변경.

L10. c6111_operator_assist.py unused import

문제: import pickle이 파일 상단에 선언되었으나 코드 어디에서도 pickle을 사용하지 않음.
근거: c6111_operator_assist.py:10. 파일 내 pickle. 호출 0건.
영향: 없음(컴파일 타임 영향 없음).
수정: import pickle 삭제.

L11. shutdown 컷오프 0건 시 플롯 루프 비정상

문제: detect_cutoffs()가 빈 리스트 반환 시 for k, w in enumerate(windows) 루프가 0회 반복 → plot이 비어 있음. matplotlib 경고(UserWarning: No artists with labels...) 발생.
근거: c6111_shutdown.py:110-118. windows가 빈 리스트일 때 plot만 생성되고 아무것도 그려지지 않음.
영향: P10 등 shutdown 이벤트 없는 컬럼에서 빈 플롯 파일 생성. 기능상 문제는 없으나 사용자 혼란.
수정: if not windows: print(" [skip] shutdown 이벤트 없음"); return 추가.

L12. PredictAsync 동기 구현 — CancellationToken 미사용

문제: PredictAsync()가 항상 Task.FromResult(Predict(...))로 동기 실행. CancellationToken 파라미터를 전혀 사용하지 않음.
근거: SteamAdvisor.cs:112-116.
영향: 현재는 항상 동기(fast operation)여서 실질 문제 없음. 비동기 체인에서 호출 시 불필요한 Task 할당만 발생.
수정: Task.FromResult 대신 ValueTask.FromResult 사용하거나, 실제 async 경로(DB 조회 등) 추가 시 token 전파.


교차 검증 통과 내역

# 항목 Q1(기수정?) Q2(타레이어?) Q3(의도?) Q4(재현?) 최종
M1 Ood/InEnv 하드코딩 No No No(실수) Yes MED
M2 feed_start 299.5 No No No Yes MED
M3 NaN 무방비 No No No Yes MED
L4 BASE 하드코딩 No 부분완화 No Yes LOW
L5 PolyVal 무경고 No - No 조건부 LOW
L6 Controller 검증누락 No No No 조건부 LOW
L7 cutin 중복 No 독립함수 No 조건부 LOW
L8 R² train-only No 정보용 No No LOW
L9 DSN 평문 No Dev전용 No No LOW
L10 pickle 미사용 No - No(생략) No LOW
L11 shutdown empty No - No Yes LOW
L12 async 동기 No - 설계(주석) No LOW

자가 검증

  • 각 지적 사항을 "현재 파일 몇 번 줄"로 직접 가리킴
  • MED 3건 모두 재현 가능한 시나리오 서술
  • 교차 검증 4개 질문을 모두 통과한 항목만 포함
  • 수정 예시가 현재 코드에 아직 적용되지 않은 내용
  • "더 좋은 방법 제안"과 "현재 코드가 틀렸다" 혼동하지 않음

변경 요약

심각도 건수 즉시 수정 권장 상태
HIGH 0
MED 3 M1(Ood): 즉시, M3(NaN): 즉시, M2(feed_start): 분석 재실행 필요 전항 수정 완료
LOW 8+1 리팩터링 시 일괄 4건 수정 완료

수정 내역 (2026-06-05)

항목 상태 변경 내용
M1 Ood/InEnv 하드코딩 수정 SteamAdvisor.cs:107Ood=false,InEnv=trueOod=!inEnv,InEnv=inEnv
M2 feed_start lookback 부족 수정+분석재실행 c6111_shutdown.py:45 — 600→1200행 확장. P9 feed_to_cutoff 중앙값 55→99분 개선 (6/22건은 여전히 599.5로 10h ceiling 도달)
M3 NaN 입력 무방비 수정 SteamAdvisor.cs:69-71double.IsNaN 가드 추가
L5 PolyVal 무경고 수정 SteamAdvisor.cs:142 — 계수 부족 시 _logger.LogWarning
L10 pickle 미사용 수정 c6111_operator_assist.py:10import pickle 삭제
L11 shutdown empty plot 수정 c6111_shutdown.py:99-101 — 컷오프 0건 시 조기 return
L12 async misleading 수정 SteamAdvisor.cs:112TaskValueTask, CancellationToken 연동
L4 BASE 하드코딩 보류 8개 파일 — 추후 리팩터링
L6 Controller 검증 누락 보류 [Required]·[Range] 속성
L7 cutin 중복 보류 from c6111_startup import detect_cutins
L8 R² train-only 보류 train/test 분할
L9 DSN 평문 보류 환경변수 전환