From f139b2abb43c1de95dbdcf5d5bb7c0cafe8a134b Mon Sep 17 00:00:00 2001 From: haneulai Date: Thu, 12 Feb 2026 10:38:22 +0900 Subject: [PATCH] feat: add comparison stats api (/plants/stats/comparison) --- app/routers/stats.py | 163 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 162 insertions(+), 1 deletion(-) diff --git a/app/routers/stats.py b/app/routers/stats.py index 27d46e2..7baeda8 100644 --- a/app/routers/stats.py +++ b/app/routers/stats.py @@ -5,8 +5,10 @@ from fastapi import APIRouter, HTTPException, Depends, Query from supabase import Client -from typing import List, Literal +from typing import List, Literal, Optional from datetime import datetime, timedelta, timezone +import calendar +import re 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") async def get_plant_stats( plant_id: str,