"""Manager Gen6 — FinOps v2: 비용 최적화·예산 예측·자원 효율화·태깅""" import uuid from datetime import datetime, timedelta from typing import Any, Dict, List, Optional from fastapi import APIRouter, HTTPException, Query from pydantic import BaseModel router = APIRouter(prefix="/api/finops2", tags=["FinOps v2"]) _budgets: Dict[str, Dict] = {} _cost_policies: Dict[str, Dict] = {} _allocations: Dict[str, Dict] = {} class BudgetCreate(BaseModel): name: str; amount: float; period: str = "monthly" # monthly|quarterly|annual category: str = "infra"; owner: str = "" alert_threshold: float = 0.8 # 80% 도달 시 알림 class CostPolicy(BaseModel): name: str; rule: str; action: str = "alert" # alert|block|scale_down threshold: float; resource_type: str = "compute" # ── 비용 현황 ──────────────────────────────────────────────────────────────── @router.get("/costs/summary") async def cost_summary(period: str = "monthly"): return {"period": period, "total": 3420000, "currency": "KRW", "breakdown": [ {"category": "compute", "amount": 1820000, "ratio": 53.2}, {"category": "storage", "amount": 820000, "ratio": 24.0}, {"category": "network", "amount": 480000, "ratio": 14.0}, {"category": "license", "amount": 300000, "ratio": 8.8}, ], "vs_last_period": -5.2, "ts": datetime.utcnow().isoformat()} @router.get("/costs/trend") async def cost_trend(months: int = 6): import random return {"months": months, "trend": [ {"month": f"2026-{6-i:02d}", "amount": round(random.uniform(3000000, 3800000))} for i in range(months) ]} @router.get("/costs/by-resource") async def cost_by_resource(resource_type: str = "compute"): return {"resource_type": resource_type, "resources": [ {"id": "app-01", "name": "Application Server 01", "cost": 680000, "usage_pct": 71.2, "efficiency": "good"}, {"id": "app-02", "name": "Application Server 02", "cost": 520000, "usage_pct": 43.1, "efficiency": "poor"}, {"id": "db-01", "name": "Database Server 01", "cost": 620000, "usage_pct": 82.4, "efficiency": "good"}, ]} # ── 예산 관리 ──────────────────────────────────────────────────────────────── @router.post("/budgets") async def create_budget(budget: BudgetCreate): bid = f"BUD-{uuid.uuid4().hex[:8].upper()}" _budgets[bid] = {**budget.model_dump(), "id": bid, "spent": 0.0, "created_at": datetime.utcnow().isoformat()} return _budgets[bid] @router.get("/budgets") async def list_budgets(): default = [ {"id": "BUD-2026-01", "name": "2026 인프라 예산", "amount": 50000000, "spent": 20520000, "ratio": 41.0, "period": "annual", "status": "on_track"}, ] custom = list(_budgets.values()) return {"budgets": default + custom, "total": len(default) + len(custom)} @router.get("/budgets/{bid}/forecast") async def budget_forecast(bid: str): b = _budgets.get(bid) return {"budget_id": bid, "forecast_eop": 42000000, "projected_ratio": 84.0, "risk": "low", "months_remaining": 6, "avg_monthly_spend": 3420000} # ── 비용 최적화 권고 ───────────────────────────────────────────────────────── @router.get("/optimizations") async def optimization_recommendations(): return {"recommendations": [ {"id": "OPT-001", "resource": "app-02", "type": "right_sizing", "current_spec": "4vCPU/8GB", "recommended": "2vCPU/4GB", "monthly_saving": 240000, "risk": "low", "confidence": 0.91}, {"id": "OPT-002", "resource": "app-03", "type": "shutdown_schedule", "detail": "야간·주말 비사용 — 스케줄 종료 권장", "monthly_saving": 180000, "risk": "low", "confidence": 0.95}, {"id": "OPT-003", "resource": "storage-old", "type": "archive", "detail": "180일 이상 미접근 데이터 아카이브 권장", "monthly_saving": 60000, "risk": "low", "confidence": 0.88}, ], "total_potential_saving": 480000, "ts": datetime.utcnow().isoformat()} @router.post("/optimizations/{oid}/apply") async def apply_optimization(oid: str, auto: bool = False): return {"optimization_id": oid, "applied": True, "auto": auto, "ts": datetime.utcnow().isoformat(), "estimated_saving": 240000} # ── 비용 정책 ──────────────────────────────────────────────────────────────── @router.post("/cost-policies") async def create_cost_policy(policy: CostPolicy): pid = f"CPOL-{uuid.uuid4().hex[:8].upper()}" _cost_policies[pid] = {**policy.model_dump(), "id": pid, "active": True, "triggered_count": 0, "created_at": datetime.utcnow().isoformat()} return _cost_policies[pid] @router.get("/cost-policies") async def list_cost_policies(): return {"policies": list(_cost_policies.values()), "total": len(_cost_policies)} # ── 태깅 거버넌스 ──────────────────────────────────────────────────────────── @router.get("/tags/compliance") async def tag_compliance(): return {"total_resources": 48, "tagged": 41, "untagged": 7, "compliance_rate": 85.4, "required_tags": ["environment", "owner", "cost_center", "project"], "untagged_resources": ["network-fw-01", "backup-storage-02"]} @router.get("/tags/allocation") async def cost_allocation_by_tag(tag_key: str = "project"): return {"tag_key": tag_key, "allocations": [ {"tag_value": "guardia", "cost": 2100000, "ratio": 61.4}, {"tag_value": "zioinfo-web", "cost": 820000, "ratio": 24.0}, {"tag_value": "shared", "cost": 500000, "ratio": 14.6}, ]} @router.get("/health") async def health(): return {"status": "ok", "budgets": len(_budgets), "policies": len(_cost_policies)}