Update app.py
Browse files
app.py
CHANGED
|
@@ -150,6 +150,44 @@ load_css()
|
|
| 150 |
# --- 3. DATA & MODEL LOADING FUNCTIONS (WITH CACHING) ---
|
| 151 |
# Checklist Items 1 & 2: Cache all heavy operations
|
| 152 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 153 |
@st.cache_data
|
| 154 |
def load_feature_data(file_path="data/final_dataset_tree.csv"):
|
| 155 |
"""Loads features and targets, converts index to datetime."""
|
|
@@ -238,6 +276,47 @@ else:
|
|
| 238 |
st.error("Could not load main data, application cannot continue.")
|
| 239 |
st.stop()
|
| 240 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 241 |
# --- 5. GIAO DIỆN SIDEBAR (ĐÃ XÓA) ---
|
| 242 |
# Toàn bộ phần sidebar đã bị xóa
|
| 243 |
|
|
@@ -248,7 +327,7 @@ tab1, tab2, tab3, tab4 = st.tabs([
|
|
| 248 |
"📑 Project Overview & Methodology",
|
| 249 |
"🌦️ Live 5-Day Forecast",
|
| 250 |
"📊 Model Performance & Diagnostics",
|
| 251 |
-
"⏱️ Hourly Prediction
|
| 252 |
])
|
| 253 |
|
| 254 |
# --- TAB 1: Project Overview ---
|
|
@@ -622,4 +701,122 @@ with tab3:
|
|
| 622 |
"around 0 and show no pattern over time.")
|
| 623 |
|
| 624 |
else:
|
| 625 |
-
st.warning("Loading performance data...")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 150 |
# --- 3. DATA & MODEL LOADING FUNCTIONS (WITH CACHING) ---
|
| 151 |
# Checklist Items 1 & 2: Cache all heavy operations
|
| 152 |
|
| 153 |
+
@st.cache_resource
|
| 154 |
+
def load_hourly_aggregate_model():
|
| 155 |
+
"""Tải mô hình Daily Champion (Hourly Aggregate) cho metric T+24h."""
|
| 156 |
+
file_path = "data/champion_Hourly_Aggregate_Champion_day_1.pkl"
|
| 157 |
+
try:
|
| 158 |
+
model = joblib.load(file_path)
|
| 159 |
+
return model
|
| 160 |
+
except FileNotFoundError as e:
|
| 161 |
+
st.warning(f"Warning: Daily Aggregate Model not found at: {e.filename}. Using dummy data for T+24h metric.")
|
| 162 |
+
return None
|
| 163 |
+
|
| 164 |
+
@st.cache_resource
|
| 165 |
+
def load_24_hourly_models():
|
| 166 |
+
"""Tải 24 mô hình LGBM chuyên biệt (T+1h đến T+24h) cho biểu đồ."""
|
| 167 |
+
hourly_models = {}
|
| 168 |
+
|
| 169 |
+
# Số lượng mô hình bạn muốn tải (chúng ta giả định có 24 file)
|
| 170 |
+
num_horizons = 24
|
| 171 |
+
|
| 172 |
+
try:
|
| 173 |
+
for h in range(1, num_horizons + 1):
|
| 174 |
+
# Giả định file model T+1h đến T+10h là file bạn gửi, các file khác nằm trong 'models/'
|
| 175 |
+
if h <= 10:
|
| 176 |
+
file_path = f"models/lgbm_model_target_temp_next_{h}h.pkl" # Sử dụng tên file bạn gửi
|
| 177 |
+
else:
|
| 178 |
+
file_path = f"models/lgbm_model_target_temp_next_{h}h.pkl" # Giả định path
|
| 179 |
+
|
| 180 |
+
model = joblib.load(file_path)
|
| 181 |
+
hourly_models[h] = model
|
| 182 |
+
|
| 183 |
+
if len(hourly_models) < num_horizons:
|
| 184 |
+
st.warning(f"Warning: Only {len(hourly_models)} hourly models loaded. Graph will be incomplete.")
|
| 185 |
+
|
| 186 |
+
return hourly_models
|
| 187 |
+
except FileNotFoundError as e:
|
| 188 |
+
st.error(f"ERROR: Missing hourly model file: {e.filename}. Cannot generate full hourly graph.")
|
| 189 |
+
return {}
|
| 190 |
+
|
| 191 |
@st.cache_data
|
| 192 |
def load_feature_data(file_path="data/final_dataset_tree.csv"):
|
| 193 |
"""Loads features and targets, converts index to datetime."""
|
|
|
|
| 276 |
st.error("Could not load main data, application cannot continue.")
|
| 277 |
st.stop()
|
| 278 |
|
| 279 |
+
|
| 280 |
+
# --- CRITICAL CUSTOMIZATION (Hourly Targets) ---
|
| 281 |
+
HOURLY_TARGET_COLS = ['target_temp_next_24h', 'target_temp_next_48h', 'target_temp_next_72h',
|
| 282 |
+
'target_temp_next_96h', 'target_temp_next_120h']
|
| 283 |
+
|
| 284 |
+
# Load models và data mới
|
| 285 |
+
hourly_data_df = load_hourly_data(file_path="data/final_hourly_features_demo.xlsx - Sheet1.csv") # Dùng tên file features demo
|
| 286 |
+
hourly_models_24h = load_24_hourly_models() # Dùng 24 mô hình LGBM
|
| 287 |
+
hourly_agg_model = load_hourly_aggregate_model() # Dùng mô hình Aggregate T+24h
|
| 288 |
+
|
| 289 |
+
# Tạo input features cho Hourly (giữ nguyên logic)
|
| 290 |
+
if not hourly_data_df.empty:
|
| 291 |
+
HOURLY_FEATURE_COLS = [col for col in hourly_data_df.columns if col not in HOURLY_TARGET_COLS]
|
| 292 |
+
X_test_hourly = hourly_data_df.loc[TEST_START_DATE:TEST_END_DATE][HOURLY_FEATURE_COLS]
|
| 293 |
+
else:
|
| 294 |
+
X_test_hourly = pd.DataFrame()
|
| 295 |
+
|
| 296 |
+
# --- Định nghĩa Hàm Dự đoán 24h Thực tế ---
|
| 297 |
+
def predict_next_24_hours(input_features: pd.DataFrame, models: dict) -> List[float]:
|
| 298 |
+
"""Chạy các mô hình LGBM chuyên biệt (T+1h đến T+24h) để tạo ra 24 điểm dự báo."""
|
| 299 |
+
predictions = []
|
| 300 |
+
num_horizons = len(models)
|
| 301 |
+
|
| 302 |
+
if input_features.empty or not models:
|
| 303 |
+
# Nếu thiếu model, tạo 24 giá trị giả lập dựa trên nhiệt độ hiện tại (temp)
|
| 304 |
+
last_temp = input_features['temp'].iloc[-1] if not input_features.empty else 28.0
|
| 305 |
+
return [last_temp + 1.5 * np.sin(2 * np.pi * (h + 10) / 24) + np.random.normal(0, 0.5)
|
| 306 |
+
for h in range(num_horizons)]
|
| 307 |
+
|
| 308 |
+
# Chạy mô hình Direct Hourly
|
| 309 |
+
for h in range(1, num_horizons + 1):
|
| 310 |
+
try:
|
| 311 |
+
model = models[h]
|
| 312 |
+
# Predict chỉ 1 giá trị
|
| 313 |
+
pred = model.predict(input_features)[0]
|
| 314 |
+
predictions.append(pred)
|
| 315 |
+
except:
|
| 316 |
+
predictions.append(float('nan'))
|
| 317 |
+
|
| 318 |
+
return predictions
|
| 319 |
+
|
| 320 |
# --- 5. GIAO DIỆN SIDEBAR (ĐÃ XÓA) ---
|
| 321 |
# Toàn bộ phần sidebar đã bị xóa
|
| 322 |
|
|
|
|
| 327 |
"📑 Project Overview & Methodology",
|
| 328 |
"🌦️ Live 5-Day Forecast",
|
| 329 |
"📊 Model Performance & Diagnostics",
|
| 330 |
+
"⏱️ Hourly Prediction" # TAB MỚI
|
| 331 |
])
|
| 332 |
|
| 333 |
# --- TAB 1: Project Overview ---
|
|
|
|
| 701 |
"around 0 and show no pattern over time.")
|
| 702 |
|
| 703 |
else:
|
| 704 |
+
st.warning("Loading performance data...")
|
| 705 |
+
|
| 706 |
+
|
| 707 |
+
# --- TAB 4: Hourly Prediction ---
|
| 708 |
+
with tab4:
|
| 709 |
+
st.title("Hourly Prediction (Next 24 Hours) 🌡️")
|
| 710 |
+
st.markdown("""
|
| 711 |
+
Phần này sử dụng **24 Mô hình Dự báo Trực tiếp Hàng giờ (Hourly Direct Model)** và các features chi tiết (`lags`, `rolling means`) để cung cấp dự báo nhiệt độ cho 24 giờ tiếp theo.
|
| 712 |
+
""")
|
| 713 |
+
|
| 714 |
+
st.subheader("Forecast Start Time")
|
| 715 |
+
|
| 716 |
+
if not X_test_hourly.empty:
|
| 717 |
+
min_date_hourly = X_test_hourly.index.min().date()
|
| 718 |
+
max_date_hourly = X_test_hourly.index.max().date()
|
| 719 |
+
|
| 720 |
+
selected_date_hourly = st.date_input(
|
| 721 |
+
"Select the day you want the forecast to START from:",
|
| 722 |
+
value=min_date_hourly,
|
| 723 |
+
min_value=min_date_hourly,
|
| 724 |
+
max_value=max_date_hourly,
|
| 725 |
+
format="YYYY-MM-DD"
|
| 726 |
+
)
|
| 727 |
+
|
| 728 |
+
latest_time_for_day = X_test_hourly.index[X_test_hourly.index.date == selected_date_hourly].max()
|
| 729 |
+
input_features_hourly = X_test_hourly.loc[[latest_time_for_day]]
|
| 730 |
+
|
| 731 |
+
st.info(f"The model runs based on data up to the latest known hour: **{latest_time_for_day.strftime('%Y-%m-%d %H:%M:%S')}**")
|
| 732 |
+
|
| 733 |
+
st.divider()
|
| 734 |
+
|
| 735 |
+
# 1. Chạy Dự đoán Hourly (GỌI HÀM THỰC TẾ)
|
| 736 |
+
predictions_24h = predict_next_24_hours(input_features_hourly, hourly_models_24h)
|
| 737 |
+
|
| 738 |
+
# --- TÍNH TOÁN METRIC T+24H CHÍNH XÁC ---
|
| 739 |
+
t_plus_24h_metric = None
|
| 740 |
+
if hourly_agg_model:
|
| 741 |
+
# Model Aggregated Day 1 (từ file bạn gửi) dự đoán T+24h
|
| 742 |
+
try:
|
| 743 |
+
t_plus_24h_metric = hourly_agg_model.predict(input_features_hourly)[0]
|
| 744 |
+
except Exception as e:
|
| 745 |
+
st.error(f"Error predicting T+24h with loaded model: {e}")
|
| 746 |
+
|
| 747 |
+
# 2. Hiển thị Dự đoán T+24h (Tức là giờ đó ngày mai)
|
| 748 |
+
st.subheader(f"Summary Forecast for Next Day (Starting {latest_time_for_day.strftime('%H:%M')})")
|
| 749 |
+
|
| 750 |
+
forecast_start_ts = latest_time_for_day + pd.Timedelta(hours=1)
|
| 751 |
+
|
| 752 |
+
col_daily_pred = st.columns(4)
|
| 753 |
+
with col_daily_pred[0]:
|
| 754 |
+
# Hiển thị T+24h (sử dụng model Aggregated thật nếu có)
|
| 755 |
+
display_value = t_plus_24h_metric if t_plus_24h_metric is not None else predictions_24h[0]
|
| 756 |
+
st.metric(
|
| 757 |
+
label=f"Forecast @ {forecast_start_ts.strftime('%H:%M')} Tomorrow (Day 1 Agg.)",
|
| 758 |
+
value=f"{display_value:.1f}°C"
|
| 759 |
+
)
|
| 760 |
+
|
| 761 |
+
# Sử dụng các giá trị từ 24 điểm dự báo để tính Max/Min/Mean 24h
|
| 762 |
+
if predictions_24h:
|
| 763 |
+
with col_daily_pred[1]:
|
| 764 |
+
st.metric(label="Next 24h Average Temp", value=f"{np.nanmean(predictions_24h):.1f}°C")
|
| 765 |
+
with col_daily_pred[2]:
|
| 766 |
+
st.metric(label="Next 24h Max Temp", value=f"{np.nanmax(predictions_24h):.1f}°C", delta="Peak Heat")
|
| 767 |
+
|
| 768 |
+
st.markdown("---")
|
| 769 |
+
|
| 770 |
+
# 3. Graph: Nhiệt độ Từng Giờ
|
| 771 |
+
st.subheader("Hourly Temperature Breakdown (T+1h to T+24h)")
|
| 772 |
+
|
| 773 |
+
hourly_index = pd.date_range(start=forecast_start_ts, periods=len(predictions_24h), freq='H')
|
| 774 |
+
df_hourly_forecast = pd.DataFrame({
|
| 775 |
+
'Time': hourly_index,
|
| 776 |
+
'Temperature': predictions_24h
|
| 777 |
+
}).set_index('Time')
|
| 778 |
+
|
| 779 |
+
fig_hourly = go.Figure()
|
| 780 |
+
fig_hourly.add_trace(go.Scatter(
|
| 781 |
+
x=df_hourly_forecast.index,
|
| 782 |
+
y=df_hourly_forecast['Temperature'],
|
| 783 |
+
mode='lines+markers',
|
| 784 |
+
name='Hourly Forecast',
|
| 785 |
+
line=dict(color='#ff6347', width=2),
|
| 786 |
+
marker=dict(size=6)
|
| 787 |
+
))
|
| 788 |
+
|
| 789 |
+
fig_hourly.update_layout(
|
| 790 |
+
title="Hourly Temperature Forecast (T+1h to T+24h)",
|
| 791 |
+
xaxis_title="Time",
|
| 792 |
+
yaxis_title="Temperature (°C)",
|
| 793 |
+
template="plotly_white",
|
| 794 |
+
legend=dict(x=0.01, y=0.99)
|
| 795 |
+
)
|
| 796 |
+
st.plotly_chart(fig_hourly, use_container_width=True)
|
| 797 |
+
|
| 798 |
+
# 4. Hiển thị Features Dùng để Dự đoán
|
| 799 |
+
st.markdown("---")
|
| 800 |
+
with st.expander("🔍 Feature Inspector: Hourly Inputs for the Forecast"):
|
| 801 |
+
if not input_features_hourly.empty:
|
| 802 |
+
important_hourly_features = [
|
| 803 |
+
'temp', 'humidity', 'windspeed', 'cloudcover',
|
| 804 |
+
'temp_lag_1h', 'humidity_lag_24h', 'temp_diff_24h',
|
| 805 |
+
'temp_roll_24h_mean', 'humidity_roll_24h_mean',
|
| 806 |
+
'hour_sin', 'day_of_year_sin'
|
| 807 |
+
]
|
| 808 |
+
|
| 809 |
+
col_h1, col_h2, col_h3 = st.columns(3)
|
| 810 |
+
|
| 811 |
+
for i, feature in enumerate(important_hourly_features):
|
| 812 |
+
if feature in input_features_hourly.columns:
|
| 813 |
+
value = input_features_hourly[feature].iloc[0]
|
| 814 |
+
label = feature.replace('_', ' ').title()
|
| 815 |
+
|
| 816 |
+
target_col = [col_h1, col_h2, col_h3][i % 3]
|
| 817 |
+
with target_col:
|
| 818 |
+
st.metric(label=label, value=f"{value:.2f}")
|
| 819 |
+
else:
|
| 820 |
+
st.warning("No hourly feature data available for the selected hour.")
|
| 821 |
+
else:
|
| 822 |
+
st.warning("Please wait... Loading hourly data or models.")
|