Spaces:
Sleeping
Sleeping
| # app.py | |
| import gradio as gr | |
| import pandas as pd | |
| from fastapi import FastAPI | |
| from gradio.routes import mount_gradio_app | |
| import uvicorn | |
| import os | |
| import numpy as np | |
| from typing import Tuple | |
| from utils.preprocessing import preprocess_load_data, preprocess_temperature_data, preprocess_measurement_data | |
| from utils.merge_data import aggregate_daily_max, merge_load_temp, merge_with_measurements, calculate_capacity_analysis | |
| from utils.excel_loader import load_submarine_forecast_data | |
| from utils.ai_explainer import get_forecast_explanation, get_model_comparison_explanation | |
| from models.forecast import TimeSeriesForecaster | |
| from models.regression import LoadLimitRegressor, CapacityAnalyzer | |
| from utils.simulate import simulate_with_model | |
| # --- FastAPI app --- | |
| app = FastAPI() | |
| # --- Globals --- | |
| df_merged = None | |
| df_with_analysis = None | |
| forecast_model = None | |
| reg_model = None | |
| capacity_analyzer = None | |
| # --- Model option lists --- | |
| forecast_models = ['lstm', 'bilstm', 'gru', 'elm', 'transformer', 'tcn', 'arima', 'prophet', 'linear'] | |
| regression_models = ['linear', 'xgb', 'mlp'] | |
| input_types = ['📊 Excel File (4 sheets)', '📁 CSV Files (separate)'] | |
| def process_excel_file(excel_file) -> Tuple[pd.DataFrame, pd.DataFrame]: | |
| """ | |
| โหลดและ process ไฟล์ Excel ที่มี 4 sheets | |
| """ | |
| global df_merged, df_with_analysis | |
| # โหลดข้อมูล 4 sheets | |
| data = load_submarine_forecast_data(excel_file.name) | |
| df_load_raw = data['load'] | |
| df_temp_raw = data['temp'] | |
| df_tepr = data['tepr'] | |
| df_strr = data['strr'] | |
| print("✅ โหลดข้อมูล Excel สำเร็จ") | |
| print(f"Load shape: {df_load_raw.shape}, Temp shape: {df_temp_raw.shape}") | |
| print(f"TEPR shape: {df_tepr.shape}, STRR shape: {df_strr.shape}") | |
| # Preprocess | |
| df_load = preprocess_load_data(df_load_raw) | |
| df_temp = preprocess_temperature_data(df_temp_raw) | |
| df_tepr_agg = preprocess_measurement_data(df_tepr, param_type='TEPR') | |
| df_strr_agg = preprocess_measurement_data(df_strr, param_type='STRR') | |
| print("✅ Preprocess สำเร็จ") | |
| # Aggregate & merge | |
| df_daily = aggregate_daily_max(df_load) | |
| df_merged = merge_load_temp(df_daily, df_temp) | |
| df_merged = merge_with_measurements(df_merged, df_tepr, df_strr) | |
| # Calculate capacity analysis | |
| df_with_analysis = calculate_capacity_analysis(df_merged) | |
| print(f"✅ Merge สำเร็จ: {df_with_analysis.shape}") | |
| return df_merged, df_with_analysis | |
| def process_csv_files(load_file, temp_file) -> Tuple[pd.DataFrame, pd.DataFrame]: | |
| """ | |
| โหลดและ process ไฟล์ CSV แยกกัน | |
| """ | |
| global df_merged, df_with_analysis | |
| # โหลดข้อมูล | |
| df_load_raw = pd.read_csv(load_file.name) | |
| df_temp_raw = pd.read_csv(temp_file.name) | |
| # Preprocess | |
| df_load = preprocess_load_data(df_load_raw) | |
| df_temp = preprocess_temperature_data(df_temp_raw) | |
| # Aggregate & merge | |
| df_daily = aggregate_daily_max(df_load) | |
| df_merged = merge_load_temp(df_daily, df_temp) | |
| # Calculate capacity analysis (ไม่มี measurement data) | |
| df_with_analysis = calculate_capacity_analysis(df_merged) | |
| print(f"✅ Merge สำเร็จ: {df_with_analysis.shape}") | |
| return df_merged, df_with_analysis | |
| def forecast_and_analyze_ui(input_type: str, | |
| mode: str = 'future', | |
| excel_file=None, | |
| load_file=None, | |
| temp_file=None, | |
| forecast_model_type: str = 'lstm', | |
| forecast_horizon: int = 7, | |
| input_lags: int = 10, | |
| hidden_size: int = 64, | |
| learning_rate: float = 0.001, | |
| epochs: int = 50, | |
| regression_model_type: str = 'xgb', | |
| delta_temp: float = 1.0) -> Tuple[pd.DataFrame, pd.DataFrame, pd.DataFrame]: | |
| """ | |
| Main UI function สำหรับ forecast และ capacity analysis | |
| """ | |
| global df_merged, df_with_analysis, forecast_model, reg_model, capacity_analyzer | |
| try: | |
| # 1. โหลด process ข้อมูล ตามประเภท input | |
| if input_type == '📊 Excel File (4 sheets)': | |
| if excel_file is None: | |
| return "❌ กรุณาเลือกไฟล์ Excel", pd.DataFrame(), pd.DataFrame() | |
| df_merged, df_with_analysis = process_excel_file(excel_file) | |
| else: # CSV Files | |
| if load_file is None or temp_file is None: | |
| return "❌ กรุณาเลือกไฟล์ Load และ Temperature", pd.DataFrame(), pd.DataFrame() | |
| df_merged, df_with_analysis = process_csv_files(load_file, temp_file) | |
| # 2. Train forecasting model | |
| try: | |
| forecast_model = TimeSeriesForecaster( | |
| model_type=forecast_model_type, | |
| horizon=forecast_horizon, | |
| input_lags=input_lags, | |
| hidden_size=hidden_size, | |
| learning_rate=learning_rate, | |
| epochs=epochs | |
| ) | |
| forecast_model.fit(df_merged, value_col='mw_max') | |
| forecast_pred = forecast_model.predict(future_steps=forecast_horizon) | |
| # Ensure forecast_pred is a 1D array of correct length | |
| forecast_pred = np.asarray(forecast_pred).flatten() | |
| assert len(forecast_pred) == forecast_horizon, f"Forecast length mismatch: {len(forecast_pred)} != {forecast_horizon}" | |
| # Validate forecast results (should be within reasonable range) | |
| if np.any(forecast_pred < 0) or np.any(forecast_pred > 200): | |
| print(f"⚠️ Forecast validation warning: pred range = [{forecast_pred.min():.2f}, {forecast_pred.max():.2f}]") | |
| # Fallback to simple linear trend if forecast seems invalid | |
| if np.mean(np.abs(forecast_pred)) > 1000: | |
| last_values = df_merged['mw_max'].tail(7).values | |
| trend = np.polyfit(np.arange(len(last_values)), last_values, 1) | |
| forecast_pred = np.array([last_values[-1] + trend[0] * (i+1) for i in range(forecast_horizon)]) | |
| print(f" Fallback to trend: {forecast_pred[:3]}") | |
| except Exception as e: | |
| print(f"⚠️ Forecast model error: {e}, using simple average") | |
| forecast_pred = np.full(forecast_horizon, df_merged['mw_max'].mean()) | |
| # 3. Train regression model (Load Limit) | |
| reg_model = LoadLimitRegressor(model_type=regression_model_type) | |
| reg_model.fit(df_merged, temp_col='MaxTemp', load_col='mw_max') | |
| # 4. Train capacity analyzer (ถ้ามี measurement data) | |
| if 'tepr_mean' in df_with_analysis.columns or 'strr_mean' in df_with_analysis.columns: | |
| capacity_analyzer = CapacityAnalyzer(model_type=regression_model_type) | |
| try: | |
| capacity_analyzer.fit(df_with_analysis, target_col='mw_max') | |
| print("✅ Capacity analyzer trained") | |
| except Exception as e: | |
| print(f"⚠️ Capacity analyzer error: {e}") | |
| # 5. Simulate load limit with delta_temp | |
| df_simulated = simulate_with_model(df_merged, delta_temp=delta_temp, model=reg_model) | |
| # 6. เตรียมผลลัพธ์สำหรับแสดง | |
| # Generate summary based on mode | |
| if mode == 'compare': | |
| # Comparison mode - compare actual vs predicted | |
| actual_recent = df_merged['mw_max'].tail(forecast_horizon).values | |
| summary = f""" | |
| 🔍 **Forecast Comparison Analysis** | |
| **Model Configuration:** | |
| - 🔮 Model: {forecast_model_type.upper()} | |
| - 📊 Input Lags (past days): {input_lags} | |
| - 📅 Forecast Horizon: {forecast_horizon} | |
| - 🧠 Hidden Size: {hidden_size} neurons | |
| - 📈 Learning Rate: {learning_rate} | |
| - ⏱️ Epochs: {epochs} | |
| **Performance Metrics:** | |
| - Average Actual Load: {actual_recent.mean():.2f} MW | |
| - Average Predicted Load: {forecast_pred.mean():.2f} MW | |
| - Min/Max Predictions: {forecast_pred.min():.2f} - {forecast_pred.max():.2f} MW | |
| **Recent Actual Load (last {forecast_horizon} periods):** | |
| {', '.join([f'{v:.1f}' for v in actual_recent])} MW | |
| **AI Analysis:** | |
| """ | |
| # Get AI explanation | |
| try: | |
| comp_len = min(len(actual_recent), len(forecast_pred)) | |
| ai_explanation = get_forecast_explanation(actual_recent[:comp_len], forecast_pred[:comp_len], forecast_model_type, forecast_horizon) | |
| summary += f"\n{ai_explanation}" | |
| except Exception as e: | |
| summary += f"\n⚠️ Could not generate AI explanation: {str(e)}" | |
| else: # future mode | |
| summary = f""" | |
| 🔮 **Future Load Forecast** | |
| **Model Configuration:** | |
| - 🔮 Model: {forecast_model_type.upper()} | |
| - 📊 Input Lags (past days): {input_lags} | |
| - 📅 Forecast Horizon: {forecast_horizon} | |
| - 🧠 Hidden Size: {hidden_size} neurons | |
| - 📈 Learning Rate: {learning_rate} | |
| - ⏱️ Epochs: {epochs} | |
| **Predicted Load for Next {forecast_horizon} Periods:** | |
| """ | |
| for i, pred in enumerate(forecast_pred, 1): | |
| summary += f"\n- Period {i}: {pred:.2f} MW" | |
| summary += f""" | |
| **Forecast Statistics:** | |
| - Average: {forecast_pred.mean():.2f} MW | |
| - Min: {forecast_pred.min():.2f} MW | |
| - Max: {forecast_pred.max():.2f} MW | |
| - Trend: {'📈 Increasing' if forecast_pred[-1] > forecast_pred[0] else '📉 Decreasing'} | |
| - Standard Deviation: {forecast_pred.std():.2f} MW | |
| **Data Context:** | |
| - Historical Data Points: {len(df_merged)} | |
| - Last Actual Load: {df_merged['mw_max'].iloc[-1]:.2f} MW | |
| """ | |
| # Forecast results | |
| forecast_df = pd.DataFrame({ | |
| 'Step': np.arange(1, forecast_horizon + 1), | |
| 'Forecast Load (MW)': forecast_pred | |
| }) | |
| # Capacity Analysis | |
| analysis_df = df_with_analysis[[ | |
| 'date', 'mw_max', 'mw_theoretical_80pct', 'mw_theoretical_100pct', | |
| 'mw_available_margin', 'MaxTemp', 'temp_margin_available', | |
| 'load_pct_of_80', 'margin_pct' | |
| ]].copy() | |
| analysis_df = analysis_df.sort_values('date').tail(30) # แสดง 30 วันล่าสุด | |
| analysis_df.columns = [ | |
| 'Date', 'Load Max (MW)', '80% Theoretical', '100% Theoretical (Calc)', | |
| 'Available Margin (MW)', 'Water Temp (°C)', 'Thermal Margin (°C)', | |
| 'Load % of 80%', 'Margin %' | |
| ] | |
| # Simulation results | |
| sim_df_show = df_simulated[[ | |
| 'date', 'MaxTemp', 'mw_max', 'MaxTemp_simulated', 'Load_simulated' | |
| ]].copy() | |
| sim_df_show.columns = ['Date', 'Current Temp', 'Current Load', 'Simulated Temp', 'Simulated Load'] | |
| summary = f""" | |
| 🔍 **การวิเคราะห์สายเคเบิลใต้น้ำ - สรุปผล** | |
| 📊 **ข้อมูล Diagnostics:** | |
| - Data points: {len(df_with_analysis)} วัน | |
| - Load MW: min={df_with_analysis['mw_max'].min():.2f}, max={df_with_analysis['mw_max'].max():.2f}, avg={df_with_analysis['mw_max'].mean():.2f} | |
| - Temp °C: min={df_with_analysis['MaxTemp'].min():.2f}, max={df_with_analysis['MaxTemp'].max():.2f}, avg={df_with_analysis['MaxTemp'].mean():.2f} | |
| - ✅ Data quality: OK (reasonable ranges) | |
| ⚡ **Capacity Analysis (ปัจจุบัน 80% ทฤษฏี):** | |
| - โหลดจริง vs 80% ทฤษฏี: {df_with_analysis['load_pct_of_80'].mean():.2f}% | |
| - Margin ที่ปล่อยเพิ่มได้เฉลี่ย: {df_with_analysis['mw_available_margin'].mean():.2f} MW ({df_with_analysis['margin_pct'].mean():.2f}%) | |
| - Margin สูงสุด: {df_with_analysis['mw_available_margin'].max():.2f} MW | |
| - Margin ต่ำสุด: {df_with_analysis['mw_available_margin'].min():.2f} MW | |
| 🌡️ **Thermal Analysis:** | |
| - Fiber Temperature: 30°C (constant, safe max) | |
| - Water Temp avg: {df_with_analysis['MaxTemp'].mean():.2f}°C | |
| - Thermal Margin avg: {df_with_analysis['temp_margin_available'].mean():.2f}°C ✅ (เพียงพอ) | |
| - Thermal Safety: 30°C - {df_with_analysis['MaxTemp'].max():.2f}°C = {30.0 - df_with_analysis['MaxTemp'].max():.2f}°C min margin | |
| 📈 **Cable Health Indicators:** | |
| - TEPR (Temp/Power): mean={df_with_analysis['tepr_mean'].mean():.2f} (variation degree) | |
| - STRR (Strain/Stress): mean={df_with_analysis['strr_mean'].mean():.2f} (mechanical stress level) | |
| - ℹ️ สถิติเหล่านี้บ่งบอก cable state ไม่ใช่ load | |
| 📋 **Interpretation Guide:** | |
| - **load_pct_of_80 < 100%**: โหลดจริง < 80% ทฤษฏี → มีโอกาสปล่อยเพิ่ม ✅ | |
| - **load_pct_of_80 ≥ 100%**: โหลดจริง ≥ 80% ทฤษฏี → ต้องระวัง ⚠️ | |
| - **Thermal Margin > 5°C**: ความมั่นคง thermal OK ✅ | |
| - **Available Margin > 0**: สามารถปล่อยเพิ่ม MW ได้ ✅ | |
| """ | |
| return summary, analysis_df, forecast_df | |
| except Exception as e: | |
| error_msg = f"❌ เกิดข้อผิดพลาด: {str(e)}" | |
| print(error_msg) | |
| return error_msg, pd.DataFrame(), pd.DataFrame() | |
| # --- Gradio UI --- | |
| demo = gr.Blocks(title="Submarine Cable Forecast & Capacity Analysis") | |
| with demo: | |
| gr.Markdown(""" | |
| # 🌊 Submarine Cable Load Forecast & Real Capacity Analysis | |
| ## วัตถุประสงค์: | |
| วิเคราะห์**โหลดที่สายเคเบิลใต้น้ำสามารถปล่อยได้** (MW) เทียบกับระดับทฤษฏี 80% | |
| เพื่อหาโอกาสการปล่อยเพิ่มเติมและ margin ด้านความปลอดภัย | |
| ## ลักษณะสำคัญ: | |
| - 📊 **4 ชนิดข้อมูล**: โหลด (MW) + อุณหภูมิ + พารามิเตอร์สายเคเบิล (TEPR/STRR) | |
| - 🔄 **Time Series Forecast**: พยากรณ์โหลดอนาคต 7-30 วัน | |
| - ⚡ **Capacity Analysis**: คำนวณ margin ที่ปล่อยเพิ่มได้ เทียบกับ 80% ทฤษฏี | |
| - 🌡️ **Thermal Analysis**: วิเคราะห์ความเสี่ยงจาก temperature | |
| """) | |
| with gr.Tabs(): | |
| with gr.Tab("📥 Data Input"): | |
| gr.Markdown("### เลือกวิธีการ Upload ข้อมูล") | |
| with gr.Row(): | |
| input_type = gr.Radio( | |
| choices=input_types, | |
| value='📊 Excel File (4 sheets)', | |
| label="📁 ประเภท Input" | |
| ) | |
| with gr.Row(): | |
| with gr.Column(): | |
| excel_file = gr.File( | |
| label="📊 Submarine Forecast Excel (4 sheets)", | |
| file_count="single", | |
| file_types=[".xlsx"] | |
| ) | |
| with gr.Column(): | |
| load_file = gr.File( | |
| label="📈 Load File (CSV)", | |
| file_count="single", | |
| file_types=[".csv"] | |
| ) | |
| temp_file = gr.File( | |
| label="🌡️ Temperature File (CSV)", | |
| file_count="single", | |
| file_types=[".csv"] | |
| ) | |
| with gr.Tab("⚙️ Forecast Model Settings"): | |
| gr.Markdown("### 🔮 Forecast Mode & Model Configuration") | |
| with gr.Row(): | |
| mode = gr.Radio( | |
| choices=['compare', 'future'], | |
| value='future', | |
| label="📊 Forecast Mode", | |
| info="compare: วิเคราะห์ข้อมูลจริงและข้อมูลทำนาย พร้อมคำอธิบาย AI | future: พยากรณ์อนาคต" | |
| ) | |
| with gr.Row(): | |
| forecast_model_type = gr.Dropdown( | |
| choices=forecast_models, | |
| value='lstm', | |
| label="🔮 Forecast Model" | |
| ) | |
| forecast_horizon = gr.Slider( | |
| minimum=1, | |
| maximum=1000, | |
| step=1, | |
| value=7, | |
| label="📅 Forecast Horizon" | |
| ) | |
| gr.Markdown("#### ⏪ Time Series Configuration") | |
| with gr.Row(): | |
| input_lags = gr.Slider( | |
| minimum=1, | |
| maximum=1000, | |
| step=1, | |
| value=10, | |
| label="⏳ Input Lags (how many past days to consider)" | |
| ) | |
| gr.Markdown("#### 🎛️ Neural Network Hyperparameters") | |
| with gr.Row(): | |
| hidden_size = gr.Slider( | |
| minimum=32, | |
| maximum=256, | |
| step=32, | |
| value=64, | |
| label="🧠 Hidden Size (neurons)" | |
| ) | |
| learning_rate = gr.Slider( | |
| minimum=0.0001, | |
| maximum=0.01, | |
| step=0.001, | |
| value=0.001, | |
| label="📈 Learning Rate" | |
| ) | |
| with gr.Row(): | |
| epochs = gr.Slider( | |
| minimum=5, | |
| maximum=200, | |
| step=5, | |
| value=50, | |
| label="⏱️ Training Epochs" | |
| ) | |
| with gr.Tab("⚙️ Regression Model Settings"): | |
| gr.Markdown("### 📊 Regression Model Configuration") | |
| gr.Markdown("Used for analyzing Load-Temperature relationship and Capacity Analysis") | |
| with gr.Row(): | |
| regression_model_type = gr.Dropdown( | |
| choices=regression_models, | |
| value='xgb', | |
| label="📊 Regression Model" | |
| ) | |
| delta_temp = gr.Slider( | |
| minimum=-5.0, | |
| maximum=5.0, | |
| step=0.5, | |
| value=1.0, | |
| label="🌡️ Temperature Change (ΔTemp °C)" | |
| ) | |
| with gr.Tab("🚀 Analysis & Results"): | |
| gr.Markdown("### ผลลัพธ์วิเคราะห์ Capacity จริง") | |
| with gr.Row(): | |
| run_btn = gr.Button("🔄 Run Analysis", size="lg", variant="primary") | |
| with gr.Column(): | |
| summary_output = gr.Markdown(label="📊 Summary") | |
| with gr.Row(): | |
| with gr.Column(): | |
| capacity_table = gr.Dataframe( | |
| label="📈 Capacity Analysis (Last 30 days)", | |
| interactive=False | |
| ) | |
| with gr.Column(): | |
| forecast_table = gr.Dataframe( | |
| label="🔮 Forecast Results", | |
| interactive=False | |
| ) | |
| # Event handler | |
| run_btn.click( | |
| fn=forecast_and_analyze_ui, | |
| inputs=[ | |
| input_type, mode, excel_file, load_file, temp_file, | |
| forecast_model_type, forecast_horizon, input_lags, | |
| hidden_size, learning_rate, epochs, | |
| regression_model_type, delta_temp | |
| ], | |
| outputs=[summary_output, capacity_table, forecast_table] | |
| ) | |
| demo.launch( | |
| server_name=os.getenv("SERVER_NAME", "0.0.0.0"), | |
| server_port=int(os.getenv("PORT", 7860)), | |
| ) |