Files
ev-charging-backend/app/routers/dashboard.py
2026-04-18 05:59:31 +09:00

132 lines
4.1 KiB
Python

"""대시보드 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()
]