105 lines
3.7 KiB
Python
105 lines
3.7 KiB
Python
"""성장일지 — 기능 성장 대시보드 (라우터 수·SR 처리·자립도 추이)."""
|
|
from __future__ import annotations
|
|
import logging
|
|
from datetime import datetime, timedelta
|
|
from pathlib import Path
|
|
from fastapi import APIRouter, Depends
|
|
from sqlalchemy import select, func as sqlfunc, desc
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
from core.auth import get_current_user
|
|
from database import get_db
|
|
from models import User, ChangelogEntry, HealthCheckResult, SelfReport, IndependenceScore
|
|
|
|
logger = logging.getLogger(__name__)
|
|
router = APIRouter(prefix="/api/growth", tags=["성장일지-대시보드"])
|
|
|
|
ITSM_ROUTER_DIR = Path("/opt/guardia/workspace/guardia-itsm/routers")
|
|
|
|
|
|
def _count_routers() -> int:
|
|
if not ITSM_ROUTER_DIR.exists():
|
|
return 0
|
|
return len([f for f in ITSM_ROUTER_DIR.glob("*.py") if not f.name.startswith("_")])
|
|
|
|
|
|
@router.get("/dashboard")
|
|
async def growth_dashboard(
|
|
db: AsyncSession = Depends(get_db),
|
|
user: User = Depends(get_current_user),
|
|
):
|
|
"""GUARDiA 성장 현황 종합 대시보드."""
|
|
router_count = _count_routers()
|
|
|
|
# 최근 건강검진 결과
|
|
hc_row = await db.execute(
|
|
select(HealthCheckResult).order_by(desc(HealthCheckResult.created_at)).limit(1)
|
|
)
|
|
last_hc = hc_row.scalar_one_or_none()
|
|
|
|
# 최근 자립도 점수
|
|
score_row = await db.execute(
|
|
select(IndependenceScore)
|
|
.where(IndependenceScore.dimension == "overall")
|
|
.order_by(desc(IndependenceScore.measured_at))
|
|
.limit(1)
|
|
)
|
|
last_score = score_row.scalar_one_or_none()
|
|
|
|
# 변경이력 30일 집계
|
|
thirty_days_ago = datetime.utcnow() - timedelta(days=30)
|
|
cl_count = await db.execute(
|
|
select(sqlfunc.count()).select_from(ChangelogEntry)
|
|
.where(ChangelogEntry.created_at >= thirty_days_ago)
|
|
)
|
|
|
|
# 주간 보고서 수
|
|
report_count = await db.execute(
|
|
select(sqlfunc.count()).select_from(SelfReport)
|
|
)
|
|
|
|
return {
|
|
"router_count": router_count,
|
|
"estimated_endpoints": router_count * 7,
|
|
"health": {
|
|
"last_check": last_hc.created_at if last_hc else None,
|
|
"status": "HEALTHY" if (last_hc and last_hc.success) else "UNKNOWN",
|
|
"passed": last_hc.passed if last_hc else 0,
|
|
"total": last_hc.total if last_hc else 69,
|
|
},
|
|
"independence": {
|
|
"score": last_score.score if last_score else 30.0,
|
|
"target": last_score.target_score if last_score else 85.0,
|
|
"dimension": last_score.dimension if last_score else "overall",
|
|
},
|
|
"changelog_30d": cl_count.scalar() or 0,
|
|
"weekly_reports": report_count.scalar() or 0,
|
|
"milestones": [
|
|
{"label": "라우터 100개", "achieved": router_count >= 100},
|
|
{"label": "자립도 50%", "achieved": (last_score.score if last_score else 0) >= 50},
|
|
{"label": "자립도 70%", "achieved": (last_score.score if last_score else 0) >= 70},
|
|
{"label": "자립도 85%", "achieved": (last_score.score if last_score else 0) >= 85},
|
|
],
|
|
"generated_at": datetime.utcnow().isoformat(),
|
|
}
|
|
|
|
|
|
@router.get("/timeline")
|
|
async def growth_timeline(
|
|
days: int = 90,
|
|
db: AsyncSession = Depends(get_db),
|
|
user: User = Depends(get_current_user),
|
|
):
|
|
"""날짜별 변경이력 타임라인."""
|
|
since = datetime.utcnow() - timedelta(days=days)
|
|
rows = await db.execute(
|
|
select(ChangelogEntry).where(ChangelogEntry.created_at >= since)
|
|
.order_by(ChangelogEntry.created_at)
|
|
.limit(200)
|
|
)
|
|
return [{
|
|
"date": e.created_at.strftime("%Y-%m-%d"),
|
|
"category": e.category,
|
|
"title": e.title,
|
|
"author": e.author,
|
|
} for e in rows.scalars().all()]
|