"""대시보드 API — 관리자 모니터링""" from datetime import datetime, timezone, timedelta from fastapi import APIRouter, Depends from sqlalchemy import select, func, and_, extract from sqlalchemy.ext.asyncio import AsyncSession from app.database import get_db from app.models import Charger, ChargingSession, ChargerStatus, SessionStatus from app.schemas import DashboardSummary router = APIRouter(prefix="/dashboard", tags=["대시보드"]) @router.get("/summary", response_model=DashboardSummary) async def get_summary(db: AsyncSession = Depends(get_db)): """대시보드 요약 — 오늘/이번달 충전 현황""" now = datetime.now(timezone.utc) today_start = now.replace(hour=0, minute=0, second=0, microsecond=0) month_start = now.replace(day=1, hour=0, minute=0, second=0, microsecond=0) # 충전기 통계 chargers = await db.execute(select(Charger).where(Charger.is_active == True)) charger_list = chargers.scalars().all() total = len(charger_list) active = sum(1 for c in charger_list if c.status != ChargerStatus.UNAVAILABLE) charging = sum(1 for c in charger_list if c.status == ChargerStatus.CHARGING) # 오늘 세션 today_q = await db.execute( select( func.count(ChargingSession.id), func.coalesce(func.sum(ChargingSession.total_bill), 0), func.coalesce(func.sum(ChargingSession.charged_wh), 0), ).where( and_( ChargingSession.status == SessionStatus.COMPLETED, ChargingSession.created_at >= today_start, ) ) ) today_row = today_q.one() # 이번달 세션 month_q = await db.execute( select( func.coalesce(func.sum(ChargingSession.total_bill), 0), func.coalesce(func.sum(ChargingSession.charged_wh), 0), ).where( and_( ChargingSession.status == SessionStatus.COMPLETED, ChargingSession.created_at >= month_start, ) ) ) month_row = month_q.one() return DashboardSummary( total_chargers=total, active_chargers=active, charging_now=charging, today_sessions=today_row[0], today_revenue=today_row[1], today_kwh=round(today_row[2] / 1000, 2), month_revenue=month_row[0], month_kwh=round(month_row[1] / 1000, 2), ) @router.get("/recent-sessions") async def recent_sessions( limit: int = 20, db: AsyncSession = Depends(get_db), ): """최근 충전 세션 목록""" result = await db.execute( select(ChargingSession) .order_by(ChargingSession.created_at.desc()) .limit(limit) ) sessions = result.scalars().all() return [ { "session_uid": s.session_uid, "charger_id": s.charger_id, "status": s.status, "charged_kwh": round(s.charged_wh / 1000, 2) if s.charged_wh else 0, "total_bill": s.total_bill, "started_at": s.started_at, "stopped_at": s.stopped_at, } for s in sessions ] @router.get("/daily-stats") async def daily_stats( days: int = 30, db: AsyncSession = Depends(get_db), ): """일별 충전 통계 (최근 N일)""" cutoff = datetime.now(timezone.utc) - timedelta(days=days) result = await db.execute( select( func.date(ChargingSession.created_at).label("date"), func.count(ChargingSession.id).label("sessions"), func.coalesce(func.sum(ChargingSession.total_bill), 0).label("revenue"), func.coalesce(func.sum(ChargingSession.charged_wh), 0).label("wh"), ) .where( and_( ChargingSession.status == SessionStatus.COMPLETED, ChargingSession.created_at >= cutoff, ) ) .group_by(func.date(ChargingSession.created_at)) .order_by(func.date(ChargingSession.created_at)) ) return [ { "date": str(row.date), "sessions": row.sessions, "revenue": row.revenue, "kwh": round(row.wh / 1000, 2), } for row in result.all() ]