132 lines
4.1 KiB
Python
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()
|
|
]
|