feat: Switch to monthly_stats table for month/year stats aggregation

This commit is contained in:
haneulai 2026-01-27 17:12:56 +09:00
parent c1223c1f14
commit 02a9149f55

View File

@ -42,65 +42,74 @@ async def get_plant_stats(
today_str = today.isoformat() today_str = today.isoformat()
# 1. 과거 데이터 조회 (daily_stats) # 1. 과거 데이터 조회 (daily_stats)
# 1. 과거 데이터 조회 (daily_stats) # 1. 과거 데이터 조회 (period에 따라 테이블 분기)
stats_data_raw = []
is_monthly_source = False
if period == "day": if period == "day":
# [일별 조회] daily_stats 테이블 사용
# 이번 달 1일부터 오늘까지 # 이번 달 1일부터 오늘까지
start_date = today.replace(day=1) start_date = today.replace(day=1)
date_filter = start_date.isoformat() 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별 종료일 필터 추가 stats_query = db.table("daily_stats") \
if period in ["month", "year"]: .select("date, total_generation") \
stats_query = stats_query.lte("date", end_date.isoformat() if period == "month" else today_str) .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: else:
stats_query = stats_query.lte("date", today_str) # [월별/연도별 조회] monthly_stats 테이블 사용
is_monthly_source = True
stats_query = stats_query.order("date", desc=False) if period == "month":
# year 파라미터가 있으면 해당 연도 1월~12월, 없으면 올해
# Supabase API Limit(1000) 우회를 위한 페이지네이션 target_year = year if year else today.year
all_stats_data = [] start_month = f"{target_year}-01"
start = 0 end_month = f"{target_year}-12" # 문자열 비교라 12월도 포함됨
batch_size = 1000 else: # year
# year 파라미터가 있으면 해당 연도부터
while True: if year:
# range는 inclusive index (Start, End) start_year = year
result = stats_query.range(start, start + batch_size - 1).execute() else:
batch = result.data start_year = today.year - 9 # 10년치
all_stats_data.extend(batch) start_month = f"{start_year}-01"
end_month = f"{today.year}-12"
if len(batch) < batch_size:
break
start += batch_size
# 데이터 맵핑 {날짜: 발전량}
data_map = {row["date"]: row["total_generation"] or 0 for row in all_stats_data}
# 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 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") \ logs_result = db.table("solar_logs") \
.select("today_kwh, created_at") \ .select("today_kwh") \
.eq("plant_id", plant_id) \ .eq("plant_id", plant_id) \
.gte("created_at", f"{today_str}T00:00:00") \ .gte("created_at", f"{today_str}T00:00:00") \
.order("created_at", desc=True) \ .order("created_at", desc=True) \
@ -109,11 +118,13 @@ async def get_plant_stats(
if logs_result.data: if logs_result.data:
today_generation = logs_result.data[0].get("today_kwh", 0.0) today_generation = logs_result.data[0].get("today_kwh", 0.0)
if today_generation > 0:
# 3. 데이터 병합 (오늘 데이터 갱신/추가) data_map[today_str] = today_generation
# solar_logs 값이 있으면 무조건 daily_stats 값보다 우선 (실시간성)
if today_generation > 0: # 월별/연도별 조회 시: '이번 달' 키에 오늘 발전량을 더해야 하는가?
data_map[today_str] = today_generation # 마이그레이션 스크립트는 daily_stats의 합을 넣었으므로 오늘 데이터도 포함됨.
# 크롤러도 실시간으로 daily 넣으면서 monthly upsert 할 예정.
# 따라서 별도 합산 불필요.
# 4. 포맷팅 및 집계 # 4. 포맷팅 및 집계
data = [] data = []
@ -158,7 +169,10 @@ async def get_plant_stats(
# 최근 10년 (또는 지정된 기간) 연도별 데이터 생성 (데이터 없으면 0) # 최근 10년 (또는 지정된 기간) 연도별 데이터 생성 (데이터 없으면 0)
data = [] data = []
target_start_year = start_date.year if year:
target_start_year = year
else:
target_start_year = today.year - 9
current_year = today.year current_year = today.year
for y in range(target_start_year, current_year + 1): for y in range(target_start_year, current_year + 1):