"""B-6 예측 유지보수 테스트""" import sys, ast, os, math os.environ.setdefault("GUARDIA_SECRET_KEY", "test-b6-secret-key-32bytes-padded!") os.environ.setdefault("DATABASE_URL", "sqlite+aiosqlite:///./test_b6.db") ok = True print("=== 1. 구문 검사 ===") files = ["core/predictive.py", "routers/predictive.py", "main.py"] for f in files: try: with open(f, encoding="utf-8") as fh: src = fh.read() ast.parse(src) print(f" OK {f}") except SyntaxError as e: print(f" ERR {f}: {e}") ok = False print("\n=== 2. core/predictive.py 함수 확인 ===") with open("core/predictive.py", encoding="utf-8") as f: pred_src = f.read() fn_checks = [ ("def linear_regression(", "선형 회귀 함수"), ("def predict_value(", "값 예측 함수"), ("def time_to_reach(", "임계값 도달 시간 계산"), ("def moving_average(", "이동 평균"), ("def detect_seasonal_pattern(", "계절성 패턴 감지"), ("async def fetch_metric_history(", "메트릭 이력 조회"), ("async def predict_metric_trend(", "메트릭 트렌드 예측"), ("async def analyze_server_health(", "서버 건강도 분석"), ("async def create_preventive_sr(", "예방 SR 자동 생성"), ("def assess_equipment_lifecycle(", "장비 수명 주기 평가"), ("async def run_lifecycle_analysis(", "수명 주기 배치 분석"), ("async def run_predictive_batch(", "예측 배치 실행"), ("PREDICTION_THRESHOLDS", "예측 임계값 설정"), ("EQUIPMENT_LIFESPAN", "장비 수명 기준"), ("TTR", "TTR 관련 로직 (time-to-reach)"), ("r_squared", "R² 결정계수"), ] for sym, desc in fn_checks: status = "OK" if sym in pred_src else "ERR" if status == "ERR": ok = False print(f" {status} {desc}") print("\n=== 3. routers/predictive.py 엔드포인트 확인 ===") with open("routers/predictive.py", encoding="utf-8") as f: router_src = f.read() endpoint_checks = [ ('@router.post("/analyze/{source}"', "POST /analyze/{source}"), ('@router.get("/health/{source}"', "GET /health/{source}"), ('@router.post("/batch"', "POST /batch"), ('@router.get("/lifecycle"', "GET /lifecycle"), ('@router.get("/lifecycle/{source}"', "GET /lifecycle/{source}"), ('@router.get("/thresholds"', "GET /thresholds"), ('@router.put("/thresholds/{metric_type}"', "PUT /thresholds/{metric}"), ('@router.get("/stats"', "GET /stats"), ('@router.post("/simulate"', "POST /simulate"), ] for sym, desc in endpoint_checks: status = "OK" if sym in router_src else "ERR" if status == "ERR": ok = False print(f" {status} {desc}") print("\n=== 4. main.py 등록 확인 ===") with open("main.py", encoding="utf-8") as f: main_src = f.read() for sym, desc in [("predictive", "predictive 임포트"), ("predictive.router", "predictive 라우터 등록")]: status = "OK" if sym in main_src else "ERR" if status == "ERR": ok = False print(f" {status} {desc}") print("\n=== 5. linear_regression 수학 검증 ===") try: import importlib.util spec = importlib.util.spec_from_file_location("pred_mod", "core/predictive.py") pred_mod = importlib.util.module_from_spec(spec) spec.loader.exec_module(pred_mod) # 완벽한 선형 데이터 (slope=2, intercept=1) x = [0.0, 1.0, 2.0, 3.0, 4.0] y = [1.0, 3.0, 5.0, 7.0, 9.0] slope, intercept, r_sq = pred_mod.linear_regression(x, y) assert abs(slope - 2.0) < 0.001, f"slope 오류: {slope}" assert abs(intercept - 1.0) < 0.001, f"intercept 오류: {intercept}" assert abs(r_sq - 1.0) < 0.001, f"R² 오류: {r_sq}" print(f" OK 완벽한 선형: slope={slope:.3f}, intercept={intercept:.3f}, R²={r_sq:.4f}") # 노이즈가 있는 데이터 import random; random.seed(42) x2 = [float(i) for i in range(50)] y2 = [2.0 * i + 10 + random.gauss(0, 1) for i in range(50)] slope2, intercept2, r_sq2 = pred_mod.linear_regression(x2, y2) assert 1.8 < slope2 < 2.2, f"노이즈 slope 범위 오류: {slope2}" assert r_sq2 > 0.95, f"R² 너무 낮음: {r_sq2}" print(f" OK 노이즈 선형: slope={slope2:.3f}, R²={r_sq2:.4f}") # 단일 샘플 (최소 입력) slope3, intercept3, r_sq3 = pred_mod.linear_regression([0.0], [5.0]) assert slope3 == 0.0, "단일샘플 slope 오류" print(f" OK 단일 샘플 처리: slope={slope3}") except AssertionError as e: print(f" ERR 선형 회귀 수학 오류: {e}") ok = False except Exception as e: print(f" ERR linear_regression 오류: {type(e).__name__}: {e}") ok = False print("\n=== 6. predict_value / time_to_reach 검증 ===") try: # y = 2x + 10, 현재 x=5 (y=20), target y=40 → x=15, delta=10 slope, intercept = 2.0, 10.0 pred_val = pred_mod.predict_value(slope, intercept, 5.0) assert abs(pred_val - 20.0) < 0.001, f"predict_value 오류: {pred_val}" print(f" OK predict_value(x=5) = {pred_val}") ttr = pred_mod.time_to_reach(slope, intercept, 5.0, 40.0) assert abs(ttr - 10.0) < 0.001, f"time_to_reach 오류: {ttr}" print(f" OK time_to_reach(y=40) = {ttr}시간 후") # 감소 추세에서는 None 반환 ttr2 = pred_mod.time_to_reach(-1.0, 100.0, 10.0, 150.0) assert ttr2 is None, f"감소 추세에서 None이어야 함: {ttr2}" print(f" OK 감소 추세 TTR = None (도달 불가)") # slope=0이면 None ttr3 = pred_mod.time_to_reach(0.0, 50.0, 0.0, 90.0) assert ttr3 is None, f"slope=0에서 None이어야 함: {ttr3}" print(f" OK slope=0 TTR = None") except AssertionError as e: print(f" ERR TTR 계산 오류: {e}") ok = False except Exception as e: print(f" ERR TTR 오류: {type(e).__name__}: {e}") ok = False print("\n=== 7. moving_average 검증 ===") try: vals = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0] ma = pred_mod.moving_average(vals, window=3) assert len(ma) == len(vals), "이동 평균 길이 오류" assert abs(ma[2] - 2.0) < 0.001, f"ma[2] 오류: {ma[2]}" # avg(1,2,3)=2 assert abs(ma[4] - 4.0) < 0.001, f"ma[4] 오류: {ma[4]}" # avg(3,4,5)=4 print(f" OK 이동 평균(window=3): {[round(v,2) for v in ma]}") except AssertionError as e: print(f" ERR 이동 평균 오류: {e}") ok = False except Exception as e: print(f" ERR moving_average 오류: {type(e).__name__}: {e}") ok = False print("\n=== 8. assess_equipment_lifecycle 검증 ===") try: from datetime import datetime, timedelta # 수명 초과 장비 (8년 된 서버, 수명 7년) old_date = datetime.utcnow() - timedelta(days=365 * 8) result = pred_mod.assess_equipment_lifecycle("SERVER", old_date) assert result["status"] == "EOL", f"EOL 판정 오류: {result['status']}" assert result["usage_pct"] >= 100.0, f"usage_pct 오류: {result['usage_pct']}" print(f" OK 8년 서버: status={result['status']}, usage={result['usage_pct']}%") # 경고 단계 (5년 된 서버 → 71% 사용) warn_date = datetime.utcnow() - timedelta(days=365 * 5) result2 = pred_mod.assess_equipment_lifecycle("SERVER", warn_date) assert result2["status"] in ("WARNING", "CRITICAL"), f"경고 판정 오류: {result2['status']}" print(f" OK 5년 서버: status={result2['status']}, usage={result2['usage_pct']}%") # 신규 장비 (1년 된 서버) new_date = datetime.utcnow() - timedelta(days=365) result3 = pred_mod.assess_equipment_lifecycle("SERVER", new_date) assert result3["status"] == "HEALTHY", f"HEALTHY 판정 오류: {result3['status']}" print(f" OK 1년 서버: status={result3['status']}, usage={result3['usage_pct']}%") # 네트워크 장비 (4년, 수명 5년 → 80%) net_date = datetime.utcnow() - timedelta(days=365 * 4) result4 = pred_mod.assess_equipment_lifecycle("NETWORK", net_date) print(f" OK 4년 네트워크: status={result4['status']}, usage={result4['usage_pct']}%") except AssertionError as e: print(f" ERR 수명 주기 평가 오류: {e}") ok = False except Exception as e: print(f" ERR assess_equipment_lifecycle 오류: {type(e).__name__}: {e}") ok = False print("\n=== 9. detect_seasonal_pattern 검증 ===") try: import math # 주기 24의 사인파 (뚜렷한 패턴) periodic_data = [50 + 20 * math.sin(2 * math.pi * i / 24) for i in range(96)] result = pred_mod.detect_seasonal_pattern(periodic_data, period=24) assert result["has_pattern"] == True, f"주기성 미감지: {result}" print(f" OK 주기성 감지: peak_index={result['peak_index']}, amplitude={result['amplitude']}") # 평탄한 데이터 (패턴 없음) flat_data = [50.0 + i * 0.01 for i in range(96)] result2 = pred_mod.detect_seasonal_pattern(flat_data, period=24) print(f" OK 평탄 데이터: has_pattern={result2['has_pattern']}") # 데이터 부족 result3 = pred_mod.detect_seasonal_pattern([1.0, 2.0], period=24) assert result3["has_pattern"] == False, "데이터 부족 패턴 감지 오류" print(f" OK 데이터 부족: has_pattern={result3['has_pattern']}") except AssertionError as e: print(f" ERR 계절성 패턴 오류: {e}") ok = False except Exception as e: print(f" ERR detect_seasonal_pattern 오류: {type(e).__name__}: {e}") ok = False print("\n=== 10. PREDICTION_THRESHOLDS 구조 검증 ===") try: thresholds = pred_mod.PREDICTION_THRESHOLDS assert isinstance(thresholds, dict), "dict가 아님" required_metrics = ["CPU_USAGE", "MEMORY_USAGE", "DISK_USAGE", "RESPONSE_TIME"] for mt in required_metrics: assert mt in thresholds, f"{mt} 없음" cfg = thresholds[mt] assert "warning" in cfg, f"{mt}.warning 없음" assert "critical" in cfg, f"{mt}.critical 없음" assert "unit" in cfg, f"{mt}.unit 없음" print(f" OK {mt}: warning={cfg['warning']}, critical={cfg['critical']} {cfg['unit']}") lifespan = pred_mod.EQUIPMENT_LIFESPAN assert "SERVER" in lifespan, "SERVER 수명 기준 없음" assert "NETWORK" in lifespan, "NETWORK 수명 기준 없음" print(f" OK 장비 수명 기준: {lifespan}") except AssertionError as e: print(f" ERR 임계값 구조 오류: {e}") ok = False except Exception as e: print(f" ERR 임계값 오류: {type(e).__name__}: {e}") ok = False print("\n=== B-6 예측 유지보수 테스트 완료 ===") if ok: print("모든 검사 통과") else: sys.exit(1)