139 lines
5.4 KiB
Python
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}
|