""" 클라우드 비용 최적화 AI Ollama 기반 비용 분석·최적화 권고·Reserved Instance 추정. 엔드포인트: GET /api/costopt/analyze — 비용 패턴 분석 POST /api/costopt/recommend — AI 최적화 권고 생성 GET /api/costopt/idle-resources — 유휴 리소스 탐지 GET /api/costopt/savings — 절감 가능 금액 추정 """ from __future__ import annotations import logging from datetime import datetime import httpx from fastapi import APIRouter, Depends from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from core.auth import get_current_user from database import get_db from models import User, MultiCloudConfig logger = logging.getLogger(__name__) router = APIRouter(prefix="/api/costopt", tags=["비용 최적화"]) OLLAMA_URL = "http://localhost:11434" async def _llm_analyze(prompt: str) -> str: try: async with httpx.AsyncClient(timeout=30) as c: r = await c.post(f"{OLLAMA_URL}/api/generate", json={ "model": "llama3", "prompt": prompt, "stream": False, }) return r.json().get("response", "").strip() if r.status_code == 200 else "" except Exception: return "" @router.get("/analyze") async def analyze_costs(db: AsyncSession = Depends(get_db), user: User = Depends(get_current_user)): rows = await db.execute(select(MultiCloudConfig).where(MultiCloudConfig.tenant_id == user.tenant_id, MultiCloudConfig.is_active == True)) providers = rows.scalars().all() return { "provider_count": len(providers), "providers": [{"name": p.name, "type": p.provider_type} for p in providers], "analysis_note": "실제 비용 데이터는 각 CSP API 연동 후 제공", "estimated_monthly": "설정 후 조회 가능", } @router.post("/recommend") async def get_recommendations( monthly_cost: float = 0, user: User = Depends(get_current_user), ): prompt = ( f"공공기관 클라우드 월 비용: {monthly_cost:,.0f}원\n\n" "다음 항목을 분석하여 절감 방안을 JSON 형식으로 제시:\n" "1. Reserved Instance 전환 가능 여부 (On-demand → RI 최대 60% 절감)\n" "2. 개발/테스트 환경 야간/주말 자동 중지 (최대 65% 절감)\n" "3. 스토리지 최적화 (미연결 볼륨, 오래된 스냅샷)\n" "4. 인스턴스 다운사이징 (사용률 10% 미만)\n" 'JSON 형식: {"recommendations": [...], "estimated_savings": 금액, "priority": "HIGH|MEDIUM|LOW"}' ) result = await _llm_analyze(prompt) import json, re match = re.search(r'\{.*\}', result, re.DOTALL) try: data = json.loads(match.group()) if match else {} except Exception: data = {"raw": result} return {"monthly_cost": monthly_cost, "recommendations": data} @router.get("/idle-resources") async def detect_idle_resources(user: User = Depends(get_current_user)): return { "idle_resources": [], "note": "CSP API 연동 후 CPU 사용률 < 10% 인스턴스 자동 탐지", "potential_savings_pct": 20, } @router.get("/savings") async def estimate_savings(user: User = Depends(get_current_user)): return { "ri_conversion": {"savings_pct": 40, "note": "On-demand → 1년 RI 전환 시"}, "schedule_optimization": {"savings_pct": 30, "note": "개발환경 야간/주말 중지 시"}, "rightsizing": {"savings_pct": 15, "note": "유휴 인스턴스 다운사이징 시"}, "total_estimated_savings_pct": 55, "last_updated": datetime.utcnow(), }