kawaiipeace's picture
Feat: Update compare and forecast mode
b171e6e
Raw
History Blame Contribute Delete
20.4 kB
# 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)),
)