""" 엑셀 파일 업로드 API - 과거 발전 데이터(Excel)를 업로드하여 daily_stats 테이블에 저장 """ import io from fastapi import APIRouter, UploadFile, File, Form, HTTPException, Depends from supabase import Client import pandas as pd from app.core.database import get_db router = APIRouter( prefix="/upload", tags=["Upload"] ) @router.post("/history") async def upload_history( file: UploadFile = File(..., description="엑셀 파일 (.xlsx, .xls)"), plant_id: str = Form(..., description="발전소 ID (예: nrems-01)"), db: Client = Depends(get_db) ) -> dict: """ 과거 발전 데이터 엑셀 업로드 엑셀 컬럼 형식: - date: 날짜 (YYYY-MM-DD) - generation: 발전량 (kWh) Args: file: 엑셀 파일 plant_id: 발전소 ID Returns: 저장 결과 메시지 """ # 1. 파일 확장자 검증 if not file.filename.endswith(('.xlsx', '.xls')): raise HTTPException( status_code=400, detail="엑셀 파일(.xlsx, .xls)만 업로드 가능합니다." ) # 2. 발전소 정보 조회 (capacity 필요) try: plant_response = db.table("plants") \ .select("id, capacity") \ .eq("id", plant_id) \ .single() \ .execute() if not plant_response.data: raise HTTPException( status_code=404, detail=f"발전소 '{plant_id}'를 찾을 수 없습니다." ) capacity = plant_response.data.get('capacity', 99.0) if capacity <= 0: capacity = 99.0 # 기본값 except HTTPException: raise except Exception as e: raise HTTPException( status_code=500, detail=f"발전소 조회 실패: {str(e)}" ) # 3. 엑셀 파일 읽기 try: contents = await file.read() df = pd.read_excel(io.BytesIO(contents)) # 컬럼 확인 required_columns = ['date', 'generation'] missing_columns = [col for col in required_columns if col not in df.columns] if missing_columns: raise HTTPException( status_code=400, detail=f"필수 컬럼이 없습니다: {missing_columns}. 엑셀에는 'date', 'generation' 컬럼이 필요합니다." ) # 빈 데이터 체크 if df.empty: raise HTTPException( status_code=400, detail="엑셀 파일에 데이터가 없습니다." ) except HTTPException: raise except Exception as e: raise HTTPException( status_code=400, detail=f"엑셀 파일 읽기 실패: {str(e)}" ) # 4. 데이터 변환 및 저장 준비 records = [] errors = [] for idx, row in df.iterrows(): try: # 날짜 파싱 date_val = row['date'] if pd.isna(date_val): errors.append(f"행 {idx+2}: 날짜가 비어있습니다.") continue if isinstance(date_val, str): date_str = date_val.strip() else: date_str = pd.to_datetime(date_val).strftime('%Y-%m-%d') # 발전량 generation = float(row['generation']) if pd.notna(row['generation']) else 0.0 # generation_hours 계산 generation_hours = round(generation / capacity, 2) if capacity > 0 else 0.0 record = { 'plant_id': plant_id, 'date': date_str, 'total_generation': round(generation, 2), 'peak_kw': 0.0, # 과거 데이터에서는 알 수 없음 'generation_hours': generation_hours } records.append(record) except Exception as e: errors.append(f"행 {idx+2}: {str(e)}") if not records: raise HTTPException( status_code=400, detail=f"저장할 유효한 데이터가 없습니다. 오류: {errors[:5]}" ) # 5. DB Upsert try: result = db.table("daily_stats").upsert( records, on_conflict="plant_id,date" ).execute() response_msg = f"총 {len(records)}건의 데이터가 저장되었습니다." if errors: response_msg += f" (오류 {len(errors)}건 스킵)" return { "status": "success", "message": response_msg, "saved_count": len(records), "error_count": len(errors), "errors": errors[:10] if errors else [] # 최대 10개 오류만 반환 } except Exception as e: raise HTTPException( status_code=500, detail=f"DB 저장 실패: {str(e)}" )