guardia-itsm/routers/legacy_modernization.py
2026-06-06 08:13:57 +09:00

326 lines
11 KiB
Python

from __future__ import annotations
from datetime import date, datetime
from typing import Optional, List
import httpx
from fastapi import APIRouter, Depends, HTTPException, Query
from pydantic import BaseModel
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, LegacySystem
router = APIRouter(prefix="/api/legacy", tags=["Legacy Modernization"])
def _tenant(user: User) -> str:
return user.inst_code or str(user.id)
async def _ollama(prompt: str) -> str:
try:
async with httpx.AsyncClient(timeout=30) as c:
r = await c.post(
"http://localhost:11434/api/generate",
json={"model": "llama3", "prompt": prompt, "stream": False},
)
return r.json().get("response", "분석 결과 없음")
except Exception:
return "AI 분석 불가 (Ollama 연결 실패)"
def _calc_risk(system: LegacySystem) -> float:
score = 0.0
if system.eol_date:
days_to_eol = (system.eol_date - date.today()).days
if days_to_eol < 0:
score += 40.0
elif days_to_eol < 90:
score += 30.0
elif days_to_eol < 365:
score += 20.0
elif days_to_eol < 730:
score += 10.0
if system.tech_debt_score:
score += min(system.tech_debt_score * 0.3, 30.0)
if system.migration_status == "NOT_STARTED":
score += 10.0
return min(score, 100.0)
# ── Pydantic 스키마 ────────────────────────────────────────────────────────────
class LegacySystemIn(BaseModel):
name: str
os_name: Optional[str] = None
os_version: Optional[str] = None
middleware: Optional[str] = None
eol_date: Optional[date] = None
migration_strategy: Optional[str] = None
tech_debt_score: Optional[float] = 0.0
notes: Optional[str] = None
class LegacySystemOut(BaseModel):
model_config = {"from_attributes": True}
id: int
name: str
os_name: Optional[str]
os_version: Optional[str]
middleware: Optional[str]
eol_date: Optional[date]
risk_score: Optional[float]
migration_strategy: Optional[str]
migration_status: Optional[str]
tech_debt_score: Optional[float]
notes: Optional[str]
created_at: datetime
class AssessmentIn(BaseModel):
system_id: int
constraints: Optional[str] = None
class MigrationPlanIn(BaseModel):
system_ids: List[int]
timeline_months: int = 12
class StatusUpdateIn(BaseModel):
migration_status: str
# ── 엔드포인트 ─────────────────────────────────────────────────────────────────
@router.get("/systems", summary="레거시 시스템 목록")
async def list_systems(
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user),
):
tid = _tenant(current_user)
rows = (await db.execute(
select(LegacySystem).where(LegacySystem.tenant_id == tid)
.order_by(LegacySystem.risk_score.desc())
)).scalars().all()
return [LegacySystemOut.model_validate(r) for r in rows]
@router.post("/systems", summary="레거시 시스템 등록")
async def create_system(
body: LegacySystemIn,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user),
):
tid = _tenant(current_user)
sys = LegacySystem(tenant_id=tid, **body.model_dump())
sys.risk_score = _calc_risk(sys)
db.add(sys)
await db.commit()
await db.refresh(sys)
return LegacySystemOut.model_validate(sys)
@router.get("/systems/{system_id}/risk", summary="EOL 위험도 자동 평가")
async def evaluate_risk(
system_id: int,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user),
):
tid = _tenant(current_user)
sys = (await db.execute(
select(LegacySystem).where(LegacySystem.tenant_id == tid, LegacySystem.id == system_id)
)).scalar_one_or_none()
if not sys:
raise HTTPException(404, "시스템을 찾을 수 없습니다")
score = _calc_risk(sys)
prompt = (
f"레거시 시스템 위험도 분석:\n"
f"- OS: {sys.os_name} {sys.os_version}\n"
f"- EOL: {sys.eol_date}\n"
f"- 기술 부채 점수: {sys.tech_debt_score}\n"
f"- 마이그레이션 상태: {sys.migration_status}\n"
f"위험 요인과 권고 조치를 간략히 설명하시오."
)
analysis = await _ollama(prompt)
sys.risk_score = score
await db.commit()
level = "CRITICAL" if score >= 70 else "HIGH" if score >= 50 else "MEDIUM" if score >= 30 else "LOW"
return {
"system_id": system_id,
"name": sys.name,
"risk_score": round(score, 1),
"risk_level": level,
"ai_analysis": analysis,
}
@router.post("/migration-plan", summary="마이그레이션 로드맵 AI 자동 생성")
async def create_migration_plan(
body: MigrationPlanIn,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user),
):
tid = _tenant(current_user)
systems = (await db.execute(
select(LegacySystem).where(
LegacySystem.tenant_id == tid,
LegacySystem.id.in_(body.system_ids),
).order_by(LegacySystem.risk_score.desc())
)).scalars().all()
if not systems:
raise HTTPException(404, "해당 시스템을 찾을 수 없습니다")
summary = "\n".join(
f"- {s.name} (OS:{s.os_name} {s.os_version}, 전략:{s.migration_strategy}, 위험:{s.risk_score:.0f})"
for s in systems
)
prompt = (
f"레거시 시스템 마이그레이션 로드맵 ({body.timeline_months}개월):\n{summary}\n"
f"단계별 마이그레이션 계획, 우선순위, 예상 공수를 JSON 형식으로 제시하시오."
)
plan = await _ollama(prompt)
return {
"timeline_months": body.timeline_months,
"systems": [{"id": s.id, "name": s.name, "risk_score": s.risk_score} for s in systems],
"roadmap": plan,
}
@router.get("/tech-debt", summary="기술 부채 지표 조회")
async def get_tech_debt(
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user),
):
tid = _tenant(current_user)
rows = (await db.execute(
select(LegacySystem).where(LegacySystem.tenant_id == tid)
.order_by(LegacySystem.tech_debt_score.desc())
)).scalars().all()
total = sum(r.tech_debt_score or 0 for r in rows)
return {
"total_debt_score": round(total, 1),
"system_count": len(rows),
"systems": [
{"id": r.id, "name": r.name, "tech_debt_score": r.tech_debt_score}
for r in rows
],
}
@router.post("/assessment", summary="현대화 준비도 평가")
async def assess_system(
body: AssessmentIn,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user),
):
tid = _tenant(current_user)
sys = (await db.execute(
select(LegacySystem).where(LegacySystem.tenant_id == tid, LegacySystem.id == body.system_id)
)).scalar_one_or_none()
if not sys:
raise HTTPException(404, "시스템을 찾을 수 없습니다")
prompt = (
f"현대화 준비도 평가:\n"
f"- 시스템: {sys.name}\n"
f"- OS: {sys.os_name} {sys.os_version}\n"
f"- 미들웨어: {sys.middleware}\n"
f"- 기술 부채: {sys.tech_debt_score}\n"
f"- 제약 사항: {body.constraints or '없음'}\n"
f"lift-and-shift / refactor / replace 중 최적 전략과 이유를 제시하시오."
)
assessment = await _ollama(prompt)
return {
"system_id": body.system_id,
"name": sys.name,
"recommended_strategy": sys.migration_strategy or "미정",
"assessment": assessment,
}
@router.get("/eol-alerts", summary="EOL 임박 시스템 알림 목록")
async def eol_alerts(
days_threshold: int = Query(365, ge=1, le=3650),
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user),
):
tid = _tenant(current_user)
rows = (await db.execute(
select(LegacySystem).where(LegacySystem.tenant_id == tid)
)).scalars().all()
alerts = []
today = date.today()
for r in rows:
if r.eol_date:
days_left = (r.eol_date - today).days
if days_left <= days_threshold:
urgency = "EXPIRED" if days_left < 0 else "CRITICAL" if days_left < 90 else "WARNING"
alerts.append({
"id": r.id,
"name": r.name,
"eol_date": str(r.eol_date),
"days_remaining": days_left,
"urgency": urgency,
})
alerts.sort(key=lambda x: x["days_remaining"])
return {"alert_count": len(alerts), "alerts": alerts}
@router.get("/migration-report/{system_id}", summary="전후 비교 보고서")
async def migration_report(
system_id: int,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user),
):
tid = _tenant(current_user)
sys = (await db.execute(
select(LegacySystem).where(LegacySystem.tenant_id == tid, LegacySystem.id == system_id)
)).scalar_one_or_none()
if not sys:
raise HTTPException(404, "시스템을 찾을 수 없습니다")
prompt = (
f"마이그레이션 전후 비교 보고서 작성:\n"
f"- 시스템: {sys.name}\n"
f"- 현재 OS: {sys.os_name} {sys.os_version}\n"
f"- 전략: {sys.migration_strategy}\n"
f"- 상태: {sys.migration_status}\n"
f"Before/After 기대 효과, 위험 감소율, 예상 비용절감을 보고서 형식으로 작성하시오."
)
report = await _ollama(prompt)
return {
"system_id": system_id,
"name": sys.name,
"current_status": sys.migration_status,
"risk_score": sys.risk_score,
"report": report,
}
@router.put("/systems/{system_id}/status", summary="마이그레이션 단계 상태 업데이트")
async def update_status(
system_id: int,
body: StatusUpdateIn,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user),
):
valid_statuses = {"NOT_STARTED", "PLANNING", "IN_PROGRESS", "TESTING", "COMPLETED", "CANCELLED"}
if body.migration_status not in valid_statuses:
raise HTTPException(400, f"유효한 상태: {valid_statuses}")
tid = _tenant(current_user)
sys = (await db.execute(
select(LegacySystem).where(LegacySystem.tenant_id == tid, LegacySystem.id == system_id)
)).scalar_one_or_none()
if not sys:
raise HTTPException(404, "시스템을 찾을 수 없습니다")
sys.migration_status = body.migration_status
await db.commit()
return {"id": system_id, "migration_status": body.migration_status, "message": "상태 업데이트 완료"}