feat: add comparison stats api (/plants/stats/comparison)

This commit is contained in:
haneulai 2026-02-12 10:38:22 +09:00
parent 0663458fc7
commit f139b2abb4

View File

@ -5,8 +5,10 @@
from fastapi import APIRouter, HTTPException, Depends, Query from fastapi import APIRouter, HTTPException, Depends, Query
from supabase import Client from supabase import Client
from typing import List, Literal from typing import List, Literal, Optional
from datetime import datetime, timedelta, timezone from datetime import datetime, timedelta, timezone
import calendar
import re
from app.core.database import get_db from app.core.database import get_db
@ -16,6 +18,165 @@ router = APIRouter(
) )
@router.get("/stats/comparison")
async def get_all_plants_comparison(
period: Literal["day", "month", "year"] = Query("day", description="통계 기간"),
date: Optional[str] = Query(None, description="날짜 (YYYY-MM-DD)"),
year: Optional[int] = Query(None, description="연도"),
month: Optional[int] = Query(None, description=""),
db: Client = Depends(get_db)
) -> dict:
"""
전체 발전소 발전량 비교 통계 조회
"""
try:
# 1. 모든 발전소 기본 정보 조회 (이름, 용량)
plants_res = db.table("plants").select("id, name, capacity").execute()
plants = {p['id']: p for p in plants_res.data}
# 결과 초기화
result_data = []
# 날짜 파라미터 처리
# KST 시간대 고려
kst_timezone = timezone(timedelta(hours=9))
now_kst = datetime.now(kst_timezone)
today = now_kst.date()
target_date = None
if date:
try:
target_date = datetime.strptime(date, "%Y-%m-%d").date()
except ValueError:
target_date = today
else:
target_date = today
target_year = year if year else target_date.year
target_month = month if month else target_date.month
# 데이터 조회 로직
stats_map = {} # plant_id -> generation
if period == "day":
date_str = target_date.isoformat()
# (A) 오늘 날짜인 경우: solar_logs 최신값 (실시간)
# 서버 시간대와 클라이언트 요청 날짜 일치 여부 확인
if target_date == today:
# 오늘 00:00:00 (KST) 이후 데이터 조회
start_dt = f"{date_str}T00:00:00"
# solar_logs에서 오늘 생성된 데이터 조회
logs_res = db.table("solar_logs") \
.select("plant_id, today_kwh") \
.gte("created_at", start_dt) \
.order("created_at", desc=True) \
.execute()
# plant_id별로 첫 번째(최신) 값만 취함
seen_plants = set()
for log in logs_res.data:
pid = log['plant_id']
if pid not in seen_plants:
val = log.get('today_kwh', 0)
if val is None: val = 0
stats_map[pid] = val
seen_plants.add(pid)
else:
# 과거: daily_stats 조회
daily_res = db.table("daily_stats") \
.select("plant_id, total_generation") \
.eq("date", date_str) \
.execute()
for row in daily_res.data:
val = row.get('total_generation', 0)
if val is None: val = 0
stats_map[row['plant_id']] = val
elif period == "month":
# 월간: monthly_stats 조회 (해당 월)
month_str = f"{target_year}-{target_month:02d}"
monthly_res = db.table("monthly_stats") \
.select("plant_id, total_generation") \
.eq("month", month_str) \
.execute()
for row in monthly_res.data:
val = row.get('total_generation', 0)
if val is None: val = 0
stats_map[row['plant_id']] = val
elif period == "year":
# 연간: monthly_stats 조회 (해당 연도 전체 합산)
start_month = f"{target_year}-01"
end_month = f"{target_year}-12"
monthly_res = db.table("monthly_stats") \
.select("plant_id, total_generation") \
.gte("month", start_month) \
.lte("month", end_month) \
.execute()
# 합산
for row in monthly_res.data:
pid = row['plant_id']
val = row.get('total_generation', 0)
if val is None: val = 0
stats_map[pid] = stats_map.get(pid, 0) + val
# 결과 조합
for pid, p_info in plants.items():
gen = stats_map.get(pid, 0) or 0
cap = p_info.get('capacity', 0) or 0
# 발전시간 계산
gen_hours = 0
if cap > 0:
if period == "day":
gen_hours = gen / cap
elif period == "month":
# 해당 월의 일수 (일평균 발전시간)
days_in_month = calendar.monthrange(target_year, target_month)[1]
gen_hours = (gen / cap) / days_in_month
elif period == "year":
# 연간 일평균 발전시간 (/365)
gen_hours = (gen / cap) / 365
result_data.append({
"plant_id": pid,
"plant_name": p_info['name'],
"capacity": cap,
"generation": round(gen, 2),
"generation_hours": round(gen_hours, 2)
})
# 발전소 이름 순 정렬 (호기 추출)
def sort_key(item):
match = re.search(r'(\d+)호기', item['plant_name'])
if match:
return int(match.group(1))
return 999
result_data.sort(key=sort_key)
return {
"status": "success",
"period": period,
"target_date": target_date.isoformat(),
"data": result_data,
"count": len(result_data)
}
except Exception as e:
raise HTTPException(
status_code=500,
detail=f"전체 통계 조회 실패: {str(e)}"
)
@router.get("/{plant_id}/stats") @router.get("/{plant_id}/stats")
async def get_plant_stats( async def get_plant_stats(
plant_id: str, plant_id: str,