import os import pandas as pd import numpy as np from typing import Dict, Tuple, Optional from pathlib import Path from datetime import datetime, timedelta # Prefer HF router via OpenAI-compatible client. Use env `HF_TOKEN`. # HF_TOKEN loaded lazily to allow dotenv loading after import def get_hf_token(): return os.environ.get('HF_TOKEN') def openai_summary(text: str, verbosity: str = 'brief', model: str = 'meta-llama/Llama-3.1-8B-Instruct:novita') -> str: HF_TOKEN = get_hf_token() if not HF_TOKEN: return None try: # Import here to avoid requiring OpenAI client unless HF_TOKEN set from openai import OpenAI client = OpenAI(base_url="https://router.huggingface.co/v1", api_key=HF_TOKEN) if verbosity == 'analyze': instruction = 'วิเคราะห์สาเหตุไฟฟ้าจากข้อมูลนี้ สรุปไม่เกิน 3-4 บรรทัด (ไทย) ระบุสาเหตุทางเทคนิค ผลกระทบต่อลูกค้าและระบบ และช่วงเวลา:' elif verbosity == 'recommend': instruction = 'วิเคราะห์สาเหตุไฟฟ้าจากข้อมูลนี้ พร้อมแนะนำการแก้ไข สรุปไม่เกิน 3-4 บรรทัด (ไทย) ระบุสาเหตุทางเทคนิค ผลกระทบต่อลูกค้าและระบบ ช่วงเวลาและข้อเสนอแนะในการป้องกัน:' prompt = f"{instruction}\n\n{text}\n\nสรุป:" completion = client.chat.completions.create( model=model, messages=[{"role": "user", "content": prompt}], max_tokens=1000, ) # Extract text from response choice = completion.choices[0] msg = choice.message content = msg.content return content.strip() if content else None except Exception: return None def calculate_outage_duration_minutes(df: pd.DataFrame) -> pd.Series: """ คำนวณระยะเวลาการขัดข้องเป็นนาที """ durations = pd.Series(index=df.index, dtype='float64') # ลองใช้คอลัมน์ต่างๆ สำหรับคำนวณระยะเวลา if 'OutageDateTime' in df.columns and 'FirstRestoDateTime' in df.columns: outage_time = pd.to_datetime(df['OutageDateTime'], dayfirst=True, errors='coerce') restore_time = pd.to_datetime(df['FirstRestoDateTime'], dayfirst=True, errors='coerce') durations = (restore_time - outage_time).dt.total_seconds() / 60 elif 'OutageDateTime' in df.columns and 'LastRestoDateTime' in df.columns: outage_time = pd.to_datetime(df['OutageDateTime'], dayfirst=True, errors='coerce') restore_time = pd.to_datetime(df['LastRestoDateTime'], dayfirst=True, errors='coerce') durations = (restore_time - outage_time).dt.total_seconds() / 60 elif 'CreateEventDateTime' in df.columns and 'CloseEventDateTime' in df.columns: create_time = pd.to_datetime(df['CreateEventDateTime'], dayfirst=True, errors='coerce') close_time = pd.to_datetime(df['CloseEventDateTime'], dayfirst=True, errors='coerce') durations = (close_time - create_time).dt.total_seconds() / 60 # แทนที่ค่าลบหรือ NaN ด้วย 0 durations = durations.fillna(0) durations = durations.where(durations >= 0, 0) return durations def compute_reliability_metrics(df: pd.DataFrame, total_customers: float) -> Dict: """ คำนวณตัวชี้วัดความน่าเชื่อถือของระบบไฟฟ้า สูตรการคำนวณ: SAIFI = Σ(λᵢ × Nᵢ) / Nₜ SAIDI = Σ(Uᵢ × Nᵢ) / Nₜ CAIDI = SAIDI / SAIFI MAIFI = Σ(λₘᵢ × Nᵢ) / Nₜ CTAIDI = Σ(rᵢ × Nᵢ × λᵢ) / Σ(Nᵢ × λᵢ) ASAI = (Nₜ × T - Σ(rᵢ × Nᵢ)) / (Nₜ × T) โดยที่: λᵢ = ความถี่การขัดข้องของจุดโหลด i Nᵢ = จำนวนลูกค้าที่ได้รับผลกระทบในจุดโหลด i Nₜ = จำนวนลูกค้าทั้งหมดในระบบ Uᵢ = ระยะเวลาขัดข้องรายปีของจุดโหลด i (นาที) rᵢ = ระยะเวลาขัดข้องเฉลี่ยต่อครั้งของจุดโหลด i (นาที) λₘᵢ = ความถี่การขัดข้องชั่วคราว (< 1 นาที) T = ระยะเวลารวม (8760 ชั่วโมง = 525,600 นาที) """ # เตรียมข้อมูล df_clean = df.copy() # คำนวณระยะเวลาขัดข้อง df_clean['duration_minutes'] = calculate_outage_duration_minutes(df_clean) # จำนวนลูกค้าที่ได้รับผลกระทบ if 'AffectedCustomer' in df_clean.columns: df_clean['affected_customers'] = pd.to_numeric(df_clean['AffectedCustomer'], errors='coerce').fillna(0) else: df_clean['affected_customers'] = 0 # แยกประเภทการขัดข้อง df_clean['is_momentary'] = df_clean['duration_minutes'] < 1 # ขัดข้องชั่วคราว < 1 นาที df_clean['is_sustained'] = df_clean['duration_minutes'] >= 1 # ขัดข้องยาวนาน >= 1 นาที # คำนวณตัวชี้วัด # 1. SAIFI - System Average Interruption Frequency Index # SAIFI = Σ(λᵢ × Nᵢ) / Nₜ sustained_interruptions = df_clean[df_clean['is_sustained']] total_customer_interruptions = sustained_interruptions['affected_customers'].sum() saifi = total_customer_interruptions / total_customers if total_customers > 0 else 0 # 2. SAIDI - System Average Interruption Duration Index # SAIDI = Σ(Uᵢ × Nᵢ) / Nₜ customer_minutes = (sustained_interruptions['duration_minutes'] * sustained_interruptions['affected_customers']).sum() saidi = customer_minutes / total_customers if total_customers > 0 else 0 # 3. CAIDI - Customer Average Interruption Duration Index # CAIDI = SAIDI / SAIFI caidi = saidi / saifi if saifi > 0 else 0 # 4. MAIFI - Momentary Average Interruption Frequency Index # MAIFI = Σ(λₘᵢ × Nᵢ) / Nₜ momentary_interruptions = df_clean[df_clean['is_momentary']] total_momentary_customer_interruptions = momentary_interruptions['affected_customers'].sum() maifi = total_momentary_customer_interruptions / total_customers if total_customers > 0 else 0 # 5. CTAIDI - Customer Total Average Interruption Duration Index # CTAIDI = Σ(rᵢ × Nᵢ × λᵢ) / Σ(Nᵢ × λᵢ) total_customer_duration = (df_clean['duration_minutes'] * df_clean['affected_customers']).sum() total_customer_events = df_clean['affected_customers'].sum() ctaidi = total_customer_duration / total_customer_events if total_customer_events > 0 else 0 # 6. ASAI - Average Service Availability Index # ASAI = (Nₜ × T - Σ(rᵢ × Nᵢ)) / (Nₜ × T) total_minutes_per_year = 525600 # 365 × 24 × 60 total_available_customer_minutes = total_customers * total_minutes_per_year total_unavailable_customer_minutes = customer_minutes asai = (total_available_customer_minutes - total_unavailable_customer_minutes) / total_available_customer_minutes if total_available_customer_minutes > 0 else 0 asai_percent = asai * 100 # 7. ASUI - Average Service Unavailability Index asui = (1 - asai) * 100 # 8. ENS - Energy Not Supplied (ประมาณการ) # สมมติว่าลูกค้าแต่ละรายใช้ไฟฟ้าเฉลี่ย 3 kW average_demand_kw = 3.0 ens_kwh = (total_unavailable_customer_minutes / 60) * average_demand_kw # 9. AENS - Average Energy Not Supplied per Customer aens = ens_kwh / total_customers if total_customers > 0 else 0 # สถิติเพิ่มเติม total_events = len(df_clean) total_sustained_events = len(sustained_interruptions) total_momentary_events = len(momentary_interruptions) avg_duration_per_event = df_clean['duration_minutes'].mean() max_duration = df_clean['duration_minutes'].max() total_affected = df_clean['affected_customers'].sum() return { # Primary Reliability Indices 'SAIFI': round(saifi, 4), 'SAIDI': round(saidi, 2), 'CAIDI': round(caidi, 2), 'MAIFI': round(maifi, 4), # Additional Indices 'CTAIDI': round(ctaidi, 2), 'ASAI': round(asai, 6), 'ASAI_percent': round(asai_percent, 4), 'ASUI_percent': round(asui, 4), 'ENS_kWh': round(ens_kwh, 2), 'AENS_kWh_per_customer': round(aens, 4), # Supporting Statistics 'total_events': total_events, 'sustained_events': total_sustained_events, 'momentary_events': total_momentary_events, 'total_customer_interruptions': int(total_customer_interruptions), 'total_customer_minutes': round(customer_minutes, 2), 'avg_duration_per_event_minutes': round(avg_duration_per_event, 2), 'max_duration_minutes': round(max_duration, 2), 'total_affected_customers': int(total_affected) } def create_reliability_dataframe(metrics: Dict) -> pd.DataFrame: """ สร้าง DataFrame สำหรับแสดงตัวชี้วัดความน่าเชื่อถือ """ reliability_data = [ { 'Metric': 'SAIFI', 'Full Name': 'System Average Interruption Frequency Index', 'Value': metrics['SAIFI'], 'Unit': 'ครั้ง/ลูกค้า/ปี', 'Formula': 'Σ(λᵢ × Nᵢ) / Nₜ', 'Description': 'ความถี่เฉลี่ยของการขัดข้องต่อลูกค้าต่อปี', 'Good_Value': '< 1.0' }, { 'Metric': 'SAIDI', 'Full Name': 'System Average Interruption Duration Index', 'Value': metrics['SAIDI'], 'Unit': 'นาที/ลูกค้า/ปี', 'Formula': 'Σ(Uᵢ × Nᵢ) / Nₜ', 'Description': 'ระยะเวลาขัดข้องเฉลี่ยต่อลูกค้าต่อปี', 'Good_Value': '< 200' }, { 'Metric': 'CAIDI', 'Full Name': 'Customer Average Interruption Duration Index', 'Value': metrics['CAIDI'], 'Unit': 'นาที/ครั้ง', 'Formula': 'SAIDI / SAIFI', 'Description': 'ระยะเวลาขัดข้องเฉลี่ยต่อครั้งที่เกิดขัดข้อง', 'Good_Value': '< 120' }, { 'Metric': 'MAIFI', 'Full Name': 'Momentary Average Interruption Frequency Index', 'Value': metrics['MAIFI'], 'Unit': 'ครั้ง/ลูกค้า/ปี', 'Formula': 'Σ(λₘᵢ × Nᵢ) / Nₜ', 'Description': 'ความถี่เฉลี่ยของการขัดข้องชั่วคราว (< 1 นาที)', 'Good_Value': '< 5.0' }, { 'Metric': 'CTAIDI', 'Full Name': 'Customer Total Average Interruption Duration Index', 'Value': metrics['CTAIDI'], 'Unit': 'นาที/ครั้ง', 'Formula': 'Σ(rᵢ × Nᵢ × λᵢ) / Σ(Nᵢ × λᵢ)', 'Description': 'ระยะเวลาขัดข้องเฉลี่ยรวมทั้งชั่วคราวและยาวนาน', 'Good_Value': '< 100' }, { 'Metric': 'ASAI', 'Full Name': 'Average Service Availability Index', 'Value': metrics['ASAI_percent'], 'Unit': '%', 'Formula': '(Nₜ×T - Σ(rᵢ×Nᵢ)) / (Nₜ×T) × 100', 'Description': 'ดัชนีความพร้อมใช้งานเฉลี่ยของระบบ', 'Good_Value': '> 99.95%' }, { 'Metric': 'ASUI', 'Full Name': 'Average Service Unavailability Index', 'Value': metrics['ASUI_percent'], 'Unit': '%', 'Formula': '100 - ASAI', 'Description': 'ดัชนีความไม่พร้อมใช้งานเฉลี่ยของระบบ', 'Good_Value': '< 0.05%' }, { 'Metric': 'AENS', 'Full Name': 'Average Energy Not Supplied per Customer', 'Value': metrics['AENS_kWh_per_customer'], 'Unit': 'kWh/ลูกค้า/ปี', 'Formula': 'ENS / Nₜ', 'Description': 'พลังงานไฟฟ้าเฉลี่ยที่ไม่สามารถจ่ายให้ลูกค้าได้', 'Good_Value': '< 10' } ] return pd.DataFrame(reliability_data) def interpret_reliability_metrics(metrics: Dict) -> str: """ ตีความผลตัวชี้วัดความน่าเชื่อถือ """ interpretations = [] # SAIFI interpretation saifi = metrics['SAIFI'] if saifi < 1.0: interpretations.append("SAIFI: ดีมาก - ลูกค้าขัดข้องน้อยกว่า 1 ครั้งต่อปี") elif saifi < 2.0: interpretations.append("SAIFI: ดี - ลูกค้าขัดข้องประมาณ 1-2 ครั้งต่อปี") elif saifi < 5.0: interpretations.append("SAIFI: พอใช้ - ลูกค้าขัดข้อง 2-5 ครั้งต่อปี") else: interpretations.append("SAIFI: ต้องปรับปรุง - ลูกค้าขัดข้องบ่อยเกิน 5 ครั้งต่อปี") # SAIDI interpretation saidi = metrics['SAIDI'] if saidi < 100: interpretations.append("SAIDI: ดีมาก - ขัดข้องน้อยกว่า 100 นาทีต่อปี") elif saidi < 200: interpretations.append("SAIDI: ดี - ขัดข้อง 100-200 นาทีต่อปี") elif saidi < 500: interpretations.append("SAIDI: พอใช้ - ขัดข้อง 200-500 นาทีต่อปี") else: interpretations.append("SAIDI: ต้องปรับปรุง - ขัดข้องเกิน 500 นาทีต่อปี") # ASAI interpretation asai = metrics['ASAI_percent'] if asai > 99.98: interpretations.append("ASAI: ดีเยี่ยม - ระบบพร้อมใช้งานเกิน 99.98%") elif asai > 99.95: interpretations.append("ASAI: ดีมาก - ระบบพร้อมใช้งาน 99.95-99.98%") elif asai > 99.90: interpretations.append("ASAI: ดี - ระบบพร้อมใช้งาน 99.90-99.95%") else: interpretations.append("ASAI: ต้องปรับปรุง - ระบบพร้อมใช้งานต่ำกว่า 99.90%") return " | ".join(interpretations) def summarize_overall(df: pd.DataFrame, use_hf: bool = False, model: str = 'meta-llama/Llama-3.1-8B-Instruct:novita', total_customers: float = None) -> Dict: """Summarize overall outage data with enhanced reliability metrics and GenAI analysis.""" # Basic statistics total_events = len(df) date_cols = ['OutageDateTime', 'FirstRestoDateTime', 'LastRestoDateTime', 'CreateEventDateTime', 'CloseEventDateTime'] # Parse dates df_copy = df.copy() for col in date_cols: if col in df_copy.columns: df_copy[col] = pd.to_datetime(df_copy[col], dayfirst=True, errors='coerce') # Calculate basic metrics if 'OutageDateTime' in df_copy.columns: date_range = f"{df_copy['OutageDateTime'].min()} ถึง {df_copy['OutageDateTime'].max()}" if pd.notna(df_copy['OutageDateTime'].min()) else "ไม่ระบุ" else: date_range = "ไม่ระบุ" # Event types event_types = df_copy.get('EventType', pd.Series()).value_counts().head(5).to_dict() # Affected customers total_affected = 0 if 'AffectedCustomer' in df_copy.columns: total_affected = pd.to_numeric(df_copy['AffectedCustomer'], errors='coerce').sum() # Create basic summary text basic_summary = f""" ข้อมูลไฟฟ้าล้มทั้งหมด: - จำนวนเหตุการณ์ทั้งหมด: {total_events} - ช่วงเวลาที่เกิดเหตุการณ์: {date_range} - ประเภทเหตุการณ์หลัก: {', '.join([f'{k}: {v}' for k, v in event_types.items()])} - จำนวนลูกค้าที่ได้รับผลกระทบทั้งหมด: {int(total_affected) if not pd.isna(total_affected) else 'ไม่ระบุ'} """ # Compute reliability metrics reliability_metrics = {} reliability_df = pd.DataFrame() reliability_summary = "" reliability_interpretation = "" if total_customers and total_customers > 0: try: reliability_metrics = compute_reliability_metrics(df_copy, total_customers) reliability_df = create_reliability_dataframe(reliability_metrics) reliability_interpretation = interpret_reliability_metrics(reliability_metrics) reliability_summary = f""" ดัชนีความน่าเชื่อถือของระบบไฟฟ้า: ตัวชี้วัดหลัก: - SAIFI: {reliability_metrics['SAIFI']:.4f} ครั้ง/ลูกค้า/ปี (ความถี่การขัดข้อง) - SAIDI: {reliability_metrics['SAIDI']:.2f} นาที/ลูกค้า/ปี (ระยะเวลาขัดข้อง) - CAIDI: {reliability_metrics['CAIDI']:.2f} นาที/ครั้ง (ระยะเวลาเฉลี่ยต่อครั้ง) - MAIFI: {reliability_metrics['MAIFI']:.4f} ครั้ง/ลูกค้า/ปี (ขัดข้องชั่วคราว) ตัวชี้วัดเพิ่มเติม: - ASAI: {reliability_metrics['ASAI_percent']:.4f}% (ความพร้อมใช้งาน) - ASUI: {reliability_metrics['ASUI_percent']:.4f}% (ความไม่พร้อมใช้งาน) - AENS: {reliability_metrics['AENS_kWh_per_customer']:.4f} kWh/ลูกค้า/ปี (พลังงานที่สูญเสีย) การตีความ: {reliability_interpretation} สถิติสนับสนุน: - เหตุการณ์ยาวนาน: {reliability_metrics['sustained_events']} ครั้ง - เหตุการณ์ชั่วคราว: {reliability_metrics['momentary_events']} ครั้ง - ระยะเวลาเฉลี่ยต่อเหตุการณ์: {reliability_metrics['avg_duration_per_event_minutes']:.2f} นาที - ระยะเวลาสูงสุด: {reliability_metrics['max_duration_minutes']:.2f} นาที """ basic_summary += reliability_summary except Exception as e: reliability_summary = f"ไม่สามารถคำนวณดัชนีความน่าเชื่อถือได้: {str(e)}" # Use GenAI for overall summary with enhanced context ai_summary = None if use_hf and get_hf_token(): try: instruction = """สรุปภาพรวมข้อมูลไฟฟ้าล้มและวิเคราะห์ตัวชี้วัดความน่าเชื่อถือ สรุปเป็น 2-3 ย่อหน้า (ไทย) ประกอบด้วย: 1. ภาพรวมเหตุการณ์และผลกระทบ 2. การวิเคราะห์ตัวชี้วัด SAIFI, SAIDI, ASAI และการตีความ 3. ข้อเสนอแนะในการปรับปรุงประสิทธิภาพและความน่าเชื่อถือของระบบ:""" prompt = f"{instruction}\n\n{basic_summary}\n\nสรุปภาพรวมและการวิเคราะห์:" ai_summary = openai_summary(prompt, verbosity='recommend', model=model) except Exception as e: ai_summary = f"ไม่สามารถสร้างสรุปด้วย AI ได้: {str(e)}" return { 'total_events': total_events, 'date_range': date_range, 'event_types': event_types, 'total_affected_customers': int(total_affected) if not pd.isna(total_affected) else None, 'basic_summary': basic_summary.strip(), 'reliability_summary': reliability_summary.strip() if reliability_summary else None, 'reliability_metrics': reliability_metrics, 'reliability_df': reliability_df, 'reliability_interpretation': reliability_interpretation, 'ai_summary': ai_summary, 'formulas': { 'SAIFI': 'Σ(λᵢ × Nᵢ) / Nₜ', 'SAIDI': 'Σ(Uᵢ × Nᵢ) / Nₜ', 'CAIDI': 'SAIDI / SAIFI', 'MAIFI': 'Σ(λₘᵢ × Nᵢ) / Nₜ', 'ASAI': '(Nₜ×T - Σ(rᵢ×Nᵢ)) / (Nₜ×T) × 100', 'AENS': 'ENS / Nₜ' } }