# ========================================== # main.py - 태양광 발전 통합 관제 시스템 # ========================================== import re from datetime import datetime # 환경 변수 로드 (최상단에서 실행) try: from dotenv import load_dotenv load_dotenv() print("✅ 환경 변수 로드 완료") except ImportError: print("⚠️ python-dotenv가 설치되지 않았습니다. 환경 변수를 직접 설정하세요.") from config import get_all_plants from database import save_to_supabase, save_to_console from crawlers import get_crawler from crawler_manager import CrawlerManager from alert_manager import AlertManager # 스마트 스케줄러 초기화 crawler_manager = CrawlerManager() def extract_unit_number(name): """발전소 이름에서 호기 번호 추출 (정렬용)""" match = re.search(r'(\d+)호기', name) if match: return int(match.group(1)) return 999 def integrated_monitoring(save_to_db=True, company_filter=None, force_run=False): """ 통합 모니터링 실행 Args: save_to_db: True면 Supabase에 저장 company_filter: 특정 업체만 필터링 (예: 'sunwind') force_run: True면 스케줄러 무시하고 강제 실행 """ now_str = datetime.now().strftime('%Y-%m-%d %H:%M:%S') print(f"\n🚀 [통합 관제 시스템] 데이터 수집 시작... ({now_str})") print("-" * 75) # 평탄화된 발전소 목록 가져오기 all_plants = get_all_plants() # 업체 필터링 (옵션) if company_filter: all_plants = [p for p in all_plants if p['company_id'] == company_filter] print(f"📌 필터 적용: {company_filter}") total_results = [] skipped_count = 0 # 알림 매니저 초기화 alert_manager = AlertManager() for plant in all_plants: plant_type = plant['type'] plant_name = plant.get('display_name', plant.get('name', 'Unknown')) company_id = plant.get('company_id', '') company_name = plant.get('company_name', '') # 크롤링 결과에서 생성되는 site_id 목록 (1,2호기 분리 처리 고려) is_split = plant.get('options', {}).get('is_split', False) if is_split: site_ids = ['nrems-01', 'nrems-02'] else: site_ids = [plant.get('id', '')] # 야간 시간대 체크 (force_run이 아닌 경우) if not force_run: # 대표 site_id 하나로 야간 여부 확인 (모든 사이트 동일 조건) representative_id = site_ids[0] if site_ids else '' if representative_id and not crawler_manager.should_run(representative_id): print(f" ⏭️ [{plant_type.upper()}] {plant_name} 스킵 (야간 시간대)") skipped_count += 1 continue print(f"📡 [{plant_type.upper()}] {company_name} - {plant_name} 수집 중...") try: crawler_func = get_crawler(plant_type) if crawler_func: data = crawler_func(plant) if data: # company_id, company_name 주입 + 변경 여부 판단 for item in data: item['company_id'] = company_id item['company_name'] = company_name item['_data_changed'] = False # 기본값: 저장 안 함 item_id = item.get('id', '') # 알림은 항상 체크 (0kW 감지 목적) alert_info = plant.copy() alert_info['id'] = item_id alert_info['name'] = item.get('name', plant_name) alert_manager.check_and_alert(alert_info, item.get('kw', 0)) if item_id: # 크롤링 성공 기록 (항상) crawler_manager.record_run(item_id) # 데이터 변경 여부 확인 # → 원격 서버가 실제로 업데이트했는지 감지 # → True면 DB 저장 대상 / False면 중복 저장 방지 if crawler_manager.check_data_change(item_id, item): crawler_manager.analyze_and_optimize(item_id) item['_data_changed'] = True else: print(f" ⏸️ [{item_id}] 데이터 변경 없음, DB 저장 스킵") # 변경된 항목만 DB 저장 대상에 포함 for item in data: if item.pop('_data_changed', False): total_results.append(item) else: print(f" ⚠️ 알 수 없는 크롤러 타입: {plant_type}") except Exception as e: print(f" ❌ {plant_name} 실패: {e}") # 정렬 (호기 번호 순) total_results.sort(key=lambda x: extract_unit_number(x['name'])) # 중복 제거 (company_id + id 조합) seen_keys = set() unique_results = [] for item in total_results: unique_key = f"{item.get('company_id', '')}_{item.get('id', '')}" if unique_key not in seen_keys: seen_keys.add(unique_key) unique_results.append(item) total_results = unique_results print("-" * 75) if skipped_count > 0: print(f"📊 스킵된 사이트: {skipped_count}개 (야간 시간대)") if total_results: # 콘솔 출력 save_to_console(total_results) # DB 저장 if save_to_db: save_to_supabase(total_results) # 이상 감지 로직 current_hour = datetime.now().hour if 10 <= current_hour <= 17: issues = [d['name'] for d in total_results if d.get('kw', 0) == 0] if issues: print("\n🚨 [이상 감지 리포트]") for name in issues: print(f" ⚠️ 경고: '{name}' 발전량이 0입니다! 확인 필요.") else: print("\n ✅ 현재 모든 발전소가 정상 가동 중입니다.") else: print("❌ 수집된 데이터가 없습니다.") return total_results if __name__ == "__main__": import sys # 인자 처리: --force 옵션으로 스케줄러 무시 force_run = '--force' in sys.argv or '-f' in sys.argv if force_run: print("⚡ [강제 실행 모드] 스케줄러 무시하고 모든 사이트 크롤링") integrated_monitoring(save_to_db=True, force_run=force_run)