guardia-itsm/routers/self_report.py

139 lines
5.4 KiB
Python

"""독립 지원 — 자율 주간 보고서 생성·발송."""
from __future__ import annotations
import logging
from datetime import datetime, timedelta
from fastapi import APIRouter, BackgroundTasks, Depends
from sqlalchemy import select, func as sqlfunc, desc
from sqlalchemy.ext.asyncio import AsyncSession
from core.auth import get_current_user, require_admin_role
from database import get_db
from models import User, SelfReport, HealthCheckResult, AutonomousAction, IndependenceScore
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/api/self-report", tags=["독립지원-보고서"])
async def _generate_report(period_label: str = None) -> dict:
"""주간 자립도 보고서 데이터 수집."""
since = datetime.utcnow() - timedelta(days=7)
period = period_label or f"{since.strftime('%Y-%m-%d')}~{datetime.utcnow().strftime('%Y-%m-%d')}"
from database import AsyncSessionLocal
async with AsyncSessionLocal() as db:
# SR 통계
try:
from models import ServiceRequest
sr_total = (await db.execute(
select(sqlfunc.count()).select_from(ServiceRequest)
.where(ServiceRequest.created_at >= since)
)).scalar() or 0
sr_auto = (await db.execute(
select(sqlfunc.count()).select_from(ServiceRequest)
.where(ServiceRequest.created_at >= since, ServiceRequest.resolved_by_ai == True)
)).scalar() or 0
except Exception:
sr_total, sr_auto = 0, 0
# 건강검진 점수 (최근 7회 평균)
hc_rows = await db.execute(
select(HealthCheckResult).order_by(desc(HealthCheckResult.created_at)).limit(7)
)
hc_list = hc_rows.scalars().all()
health_score = (sum(h.passed / (h.total or 1) * 100 for h in hc_list) / len(hc_list)) if hc_list else 0.0
# 자동 수복 횟수
auto_heals = (await db.execute(
select(sqlfunc.count()).select_from(AutonomousAction)
.where(AutonomousAction.executed_at >= since, AutonomousAction.success == True)
)).scalar() or 0
# 최신 자립도
score_row = await db.execute(
select(IndependenceScore)
.where(IndependenceScore.dimension == "overall")
.order_by(desc(IndependenceScore.measured_at)).limit(1)
)
latest_score = score_row.scalar_one_or_none()
indep = latest_score.score if latest_score else 30.0
summary_lines = [
f"📊 주간 보고서 ({period})",
f" SR 처리: {sr_total}건 (AI 자동처리: {sr_auto}건, {round(sr_auto/sr_total*100) if sr_total else 0}%)",
f" 건강점수: {health_score:.1f}%",
f" 자가수복: {auto_heals}",
f" 자립도: {indep:.1f}%",
]
summary = "\n".join(summary_lines)
report = SelfReport(
period=period, sr_total=sr_total, sr_auto_handled=sr_auto,
health_score=health_score, auto_heals=auto_heals, incidents=0,
summary=summary, created_at=datetime.utcnow(),
)
db.add(report)
await db.commit()
await db.refresh(report)
return {"id": report.id, "period": period, "summary": summary,
"sr_total": sr_total, "sr_auto": sr_auto, "health_score": health_score,
"auto_heals": auto_heals, "independence": indep}
async def _send_report(period_label: str = None):
data = await _generate_report(period_label)
try:
import httpx
async with httpx.AsyncClient(timeout=5) as c:
await c.post("http://127.0.0.1:9001/api/messenger/webhook", json={
"event": "weekly_self_report",
"room": "ops",
"message": data["summary"],
})
except Exception as e:
logger.warning("보고서 발송 실패: %s", e)
@router.post("/generate")
async def generate_report(
background_tasks: BackgroundTasks,
user: User = Depends(require_admin_role),
):
"""주간 보고서 즉시 생성·발송."""
background_tasks.add_task(_send_report)
return {"ok": True, "message": "보고서 생성 + 메신저 발송 시작됨 (백그라운드)"}
@router.get("/list")
async def list_reports(
limit: int = 10,
db: AsyncSession = Depends(get_db),
user: User = Depends(get_current_user),
):
"""보고서 이력."""
rows = await db.execute(
select(SelfReport).order_by(desc(SelfReport.created_at)).limit(limit)
)
return [{
"id": r.id, "period": r.period, "sr_total": r.sr_total,
"sr_auto_handled": r.sr_auto_handled, "health_score": r.health_score,
"auto_heals": r.auto_heals, "summary": r.summary, "created_at": r.created_at,
} for r in rows.scalars().all()]
@router.get("/latest")
async def latest_report(
db: AsyncSession = Depends(get_db),
user: User = Depends(get_current_user),
):
"""최신 보고서."""
row = await db.execute(
select(SelfReport).order_by(desc(SelfReport.created_at)).limit(1)
)
r = row.scalar_one_or_none()
if not r:
return {"message": "보고서 없음. POST /api/self-report/generate 실행"}
return {"id": r.id, "period": r.period, "summary": r.summary,
"sr_total": r.sr_total, "sr_auto_handled": r.sr_auto_handled,
"health_score": r.health_score, "auto_heals": r.auto_heals,
"created_at": r.created_at}