fix: 크롤링 오류 시 daily_stats 0으로 덮어쓰기 방지

- save_to_supabase()에서 status에 '오류' 포함 시 daily_stats 갱신 금지
- today_kwh == 0인 경우도 daily_stats 갱신 금지 (새벽/야간 보호)
- solar_logs는 이상 이력 추적을 위해 오류 데이터도 계속 기록
- 재현 사례: 2026-05-14 15:40 nrems-03 NREMS 응답 오류로 0kW 오보
This commit is contained in:
haneulai 2026-05-14 17:56:08 +09:00
parent c736b2982f
commit c885bab007

View File

@ -73,11 +73,16 @@ def save_to_supabase(data_list):
kst = timezone(timedelta(hours=9)) kst = timezone(timedelta(hours=9))
kst_now = datetime.now(kst).isoformat() kst_now = datetime.now(kst).isoformat()
status = item.get('status', '')
is_error = '오류' in status # '🔴 오류' 상태 감지
# [보호] 오류 상태 데이터는 solar_logs에는 기록하되 daily_stats는 건드리지 않음
# 단, solar_logs 기록 자체는 이상 이력 추적을 위해 유지
record = { record = {
'plant_id': plant_id, 'plant_id': plant_id,
'current_kw': float(item.get('kw', 0)), 'current_kw': float(item.get('kw', 0)),
'today_kwh': float(item.get('today', 0)), 'today_kwh': float(item.get('today', 0)),
'status': item.get('status', ''), 'status': status,
'created_at': kst_now # 한국 시간으로 저장 'created_at': kst_now # 한국 시간으로 저장
} }
records.append(record) records.append(record)
@ -86,28 +91,34 @@ def save_to_supabase(data_list):
print("[DB] 저장할 유효한 레코드가 없습니다.") print("[DB] 저장할 유효한 레코드가 없습니다.")
return False return False
# Supabase에 일괄 삽입 (solar_logs) # Supabase에 일괄 삽입 (solar_logs) - 오류 상태 포함 전체 기록
result = client.table("solar_logs").insert(records).execute() result = client.table("solar_logs").insert(records).execute()
print(f"✅ [DB] Supabase 저장 완료: {len(records)}건 (solar_logs)") print(f"✅ [DB] Supabase 저장 완료: {len(records)}건 (solar_logs)")
# daily_stats 테이블 업데이트 (Upsert) # daily_stats 테이블 업데이트 (Upsert)
# 오늘 날짜(KST) 기준, 현재 수집된 today_kwh가 기존 값보다 크거나 같으면 업데이트 # [보호 로직]
# 하지만 보통 today_kwh는 누적값이므로 간단하게 upsert 처리 # 1. 오류 상태(크롤링 실패)인 경우 daily_stats 갱신 금지
# 2. today_kwh == 0인 경우 daily_stats 갱신 금지 (새벽 0 값으로 하루치 덮어쓰기 방지)
daily_records = [] daily_records = []
kst_date_str = datetime.now(timezone(timedelta(hours=9))).strftime("%Y-%m-%d") kst_date_str = datetime.now(timezone(timedelta(hours=9))).strftime("%Y-%m-%d")
for item in data_list: for item in data_list:
plant_id = item.get('id', '') plant_id = item.get('id', '')
if not plant_id: continue if not plant_id:
continue
status = item.get('status', '')
is_error = '오류' in status
today_val = float(item.get('today', 0)) today_val = float(item.get('today', 0))
# 0인 경우는 저장하지 않거나(새벽), 기존 값을 덮어쓰지 않도록 주의해야 함 # 오류 상태이거나 today_kwh가 0이면 daily_stats 갱신 건너뜀
# 하지만 발전소 데이터 보정을 위해 0이어도 일단 기록하거나, if is_error:
# 아니면 max 값을 유지하는 로직이 필요할 수 있음. print(f" ⚠️ [{plant_id}] 오류 상태 → daily_stats 갱신 건너뜀")
# 여기서는 Upsert로 덮어쓰되, DB 트리거가 없다면 마지막 값이 저장됨. continue
# 보통 크롤링은 누적값이므로 마지막 값이 그날의 최종값에 가까움. if today_val == 0:
print(f" ⚠️ [{plant_id}] today_kwh=0 → daily_stats 갱신 건너뜀 (새벽/야간 추정)")
continue
daily_records.append({ daily_records.append({
"plant_id": plant_id, "plant_id": plant_id,
@ -118,10 +129,7 @@ def save_to_supabase(data_list):
}) })
if daily_records: if daily_records:
# upsert: plant_id, date가 unique constraint여야 함
try: try:
# ignore_duplicates=False -> 업데이트
# on_conflict="plant_id, date" (Supabase/PG 설정에 따라 다름, 보통 PK나 UK 기준)
stats_result = client.table("daily_stats").upsert(daily_records, on_conflict="plant_id, date").execute() stats_result = client.table("daily_stats").upsert(daily_records, on_conflict="plant_id, date").execute()
print(f"✅ [DB] daily_stats 업데이트 완료: {len(daily_records)}") print(f"✅ [DB] daily_stats 업데이트 완료: {len(daily_records)}")
except Exception as e: except Exception as e: