diff --git a/app/routers/stats.py b/app/routers/stats.py index f801f5e..86dda24 100644 --- a/app/routers/stats.py +++ b/app/routers/stats.py @@ -42,65 +42,74 @@ async def get_plant_stats( today_str = today.isoformat() # 1. 과거 데이터 조회 (daily_stats) - # 1. 과거 데이터 조회 (daily_stats) + # 1. 과거 데이터 조회 (period에 따라 테이블 분기) + stats_data_raw = [] + is_monthly_source = False + if period == "day": + # [일별 조회] daily_stats 테이블 사용 # 이번 달 1일부터 오늘까지 start_date = today.replace(day=1) date_filter = start_date.isoformat() - elif period == "month": - # year 파라미터가 있으면 해당 연도 1월~12월, 없으면 올해 - target_year = year if year else today.year - start_date = datetime(target_year, 1, 1).date() - # 종료일: 해당 연도 12월 31일 또는 오늘 중 작은 값 - end_date = min(datetime(target_year, 12, 31).date(), today) - date_filter = start_date.isoformat() - else: # year - # year 파라미터가 있으면 해당 연도부터, 없으면 최근 10년 - if year: - start_year = year - else: - start_year = today.year - 9 # 10년치 데이터 (올해 포함) - start_date = datetime(start_year, 1, 1).date() - end_date = today - date_filter = start_date.strftime("%Y-%m-%d") - - stats_query = db.table("daily_stats") \ - .select("date, total_generation") \ - .eq("plant_id", plant_id) \ - .gte("date", date_filter) - # period별 종료일 필터 추가 - if period in ["month", "year"]: - stats_query = stats_query.lte("date", end_date.isoformat() if period == "month" else today_str) + stats_query = db.table("daily_stats") \ + .select("date, total_generation") \ + .eq("plant_id", plant_id) \ + .gte("date", date_filter) \ + .lte("date", today_str) \ + .order("date", desc=False) + + # 페이지네이션 등 없이 심플하게 (일별은 데이터 적음) + stats_data_raw = stats_query.execute().data + else: - stats_query = stats_query.lte("date", today_str) + # [월별/연도별 조회] monthly_stats 테이블 사용 + is_monthly_source = True - stats_query = stats_query.order("date", desc=False) - - # Supabase API Limit(1000) 우회를 위한 페이지네이션 - all_stats_data = [] - start = 0 - batch_size = 1000 - - while True: - # range는 inclusive index (Start, End) - result = stats_query.range(start, start + batch_size - 1).execute() - batch = result.data - all_stats_data.extend(batch) - - if len(batch) < batch_size: - break - start += batch_size - - # 데이터 맵핑 {날짜: 발전량} - data_map = {row["date"]: row["total_generation"] or 0 for row in all_stats_data} + if period == "month": + # year 파라미터가 있으면 해당 연도 1월~12월, 없으면 올해 + target_year = year if year else today.year + start_month = f"{target_year}-01" + end_month = f"{target_year}-12" # 문자열 비교라 12월도 포함됨 + else: # year + # year 파라미터가 있으면 해당 연도부터 + if year: + start_year = year + else: + start_year = today.year - 9 # 10년치 + start_month = f"{start_year}-01" + end_month = f"{today.year}-12" - # 2. 오늘 실시간 데이터 조회 (solar_logs) - 오늘이 조회 범위에 포함될 때만 + stats_query = db.table("monthly_stats") \ + .select("month, total_generation") \ + .eq("plant_id", plant_id) \ + .gte("month", start_month) \ + .lte("month", end_month) \ + .order("month", desc=False) + + stats_data_raw = stats_query.execute().data + + # 데이터 맵핑 {날짜키: 발전량} + # daily_stats: key='date' (YYYY-MM-DD) + # monthly_stats: key='month' (YYYY-MM) + data_map = {} + for row in stats_data_raw: + key = row.get("month") if is_monthly_source else row.get("date") + val = row.get("total_generation") or 0 + if key: + data_map[key] = val + + # 2. 오늘 실시간 데이터 조회 (solar_logs) + # period='day'일 때만 합산 (monthly/year는 monthly_stats가 이미 갱신되었다고 가정하거나, 필요시 로직 추가) + # 하지만 monthly_stats가 '어제까지'의 합계일 수 있으므로, '이번 달' 데이터에는 오늘 발전량을 더해주는 게 안전함. + # 그러나 로직 복잡성을 피하기 위해, 크롤러가 실시간으로 monthly_stats를 갱신한다고 가정하고 여기선 생략 가능. + # 기존 로직 유지: 'day'일 때는 무조건 덮어쓰기. + today_generation = 0.0 - if period == "day" or (period == "month" and (not year or year == today.year)) or period == "year": - # 오늘의 가장 마지막 기록 1건만 조회 (성능 최적화) + # 일별 조회 시 오늘 데이터 덮어쓰기 + if period == "day": logs_result = db.table("solar_logs") \ - .select("today_kwh, created_at") \ + .select("today_kwh") \ .eq("plant_id", plant_id) \ .gte("created_at", f"{today_str}T00:00:00") \ .order("created_at", desc=True) \ @@ -109,11 +118,13 @@ async def get_plant_stats( if logs_result.data: today_generation = logs_result.data[0].get("today_kwh", 0.0) - - # 3. 데이터 병합 (오늘 데이터 갱신/추가) - # solar_logs 값이 있으면 무조건 daily_stats 값보다 우선 (실시간성) - if today_generation > 0: - data_map[today_str] = today_generation + if today_generation > 0: + data_map[today_str] = today_generation + + # 월별/연도별 조회 시: '이번 달' 키에 오늘 발전량을 더해야 하는가? + # 마이그레이션 스크립트는 daily_stats의 합을 넣었으므로 오늘 데이터도 포함됨. + # 크롤러도 실시간으로 daily 넣으면서 monthly upsert 할 예정. + # 따라서 별도 합산 불필요. # 4. 포맷팅 및 집계 data = [] @@ -158,7 +169,10 @@ async def get_plant_stats( # 최근 10년 (또는 지정된 기간) 연도별 데이터 생성 (데이터 없으면 0) data = [] - target_start_year = start_date.year + if year: + target_start_year = year + else: + target_start_year = today.year - 9 current_year = today.year for y in range(target_start_year, current_year + 1):