import os import joblib import numpy as np import pandas as pd import streamlit as st # ----------------------------- # Page configuration # ----------------------------- st.set_page_config( page_title="Smart Building HVAC Energy Optimization", page_icon="🏢", layout="wide" ) st.title("🏢 Smart Building Energy Prediction and HVAC Optimization") st.write( "This app predicts building energy demand for Electricity and Chilled Water " "and provides HVAC-related operational recommendations." ) # ----------------------------- # File paths # ----------------------------- BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) MODELS_DIR = os.path.join(BASE_DIR, "model") DATA_DIR = os.path.join(BASE_DIR, "data", "processed") # Prefer Day 5 final model if available, otherwise use Day 4 model final_model_path = os.path.join(MODELS_DIR, "final_energy_prediction_model.pkl") day4_model_path = os.path.join(MODELS_DIR, "best_energy_prediction_model.pkl") final_feature_path = os.path.join(MODELS_DIR, "final_feature_columns.pkl") day4_feature_path = os.path.join(MODELS_DIR, "feature_columns.pkl") train_data_path = os.path.join(DATA_DIR, "train_model_data.csv") # ----------------------------- # Load model and features # ----------------------------- @st.cache_resource def load_model_and_features(): if os.path.exists(final_model_path): model_path = final_model_path elif os.path.exists(day4_model_path): model_path = day4_model_path else: raise FileNotFoundError("No trained model found in the models folder.") if os.path.exists(final_feature_path): feature_path = final_feature_path elif os.path.exists(day4_feature_path): feature_path = day4_feature_path else: raise FileNotFoundError("No feature columns file found in the models folder.") model = joblib.load(model_path) feature_cols = joblib.load(feature_path) return model, feature_cols, model_path @st.cache_data def load_training_data(): return pd.read_csv(train_data_path) try: model, feature_cols, loaded_model_path = load_model_and_features() train_df = load_training_data() st.success("Model loaded successfully.") st.caption(f"Loaded model: {loaded_model_path}") except Exception as e: st.error(f"Error loading model or data: {e}") st.stop() # ----------------------------- # Sidebar inputs # ----------------------------- st.sidebar.header("Input Building and Weather Details") building_ids = sorted(train_df["building_id"].unique().tolist()) building_id = st.sidebar.selectbox( "Select Building ID", building_ids ) meter_label = st.sidebar.selectbox( "Select Meter Type", ["Electricity", "Chilled Water"] ) meter = 0 if meter_label == "Electricity" else 1 # Primary use options from encoded columns or original train data if "primary_use" in train_df.columns: primary_use_options = sorted(train_df["primary_use"].unique().tolist()) else: primary_use_options = [ "Education", "Office", "Entertainment/public assembly", "Lodging/residential", "Public services", "Healthcare" ] primary_use = st.sidebar.selectbox( "Building Type", primary_use_options ) square_feet = st.sidebar.number_input( "Building Area in Square Feet", min_value=1000, max_value=1000000, value=100000, step=1000 ) air_temperature = st.sidebar.number_input( "Air Temperature", min_value=-20.0, max_value=50.0, value=25.0, step=0.5 ) dew_temperature = st.sidebar.number_input( "Dew Temperature", min_value=-30.0, max_value=40.0, value=15.0, step=0.5 ) sea_level_pressure = st.sidebar.number_input( "Sea Level Pressure", min_value=900.0, max_value=1100.0, value=1013.0, step=1.0 ) wind_speed = st.sidebar.number_input( "Wind Speed", min_value=0.0, max_value=30.0, value=3.0, step=0.5 ) wind_direction = st.sidebar.number_input( "Wind Direction", min_value=0, max_value=360, value=180, step=10 ) cloud_coverage = st.sidebar.slider( "Cloud Coverage", min_value=0, max_value=9, value=3 ) precip_depth_1_hr = st.sidebar.number_input( "Precipitation Depth 1 Hour", min_value=-1.0, max_value=100.0, value=0.0, step=0.5 ) hour = st.sidebar.slider( "Hour of Day", min_value=0, max_value=23, value=14 ) day = st.sidebar.slider( "Day of Month", min_value=1, max_value=31, value=15 ) month = st.sidebar.slider( "Month", min_value=1, max_value=12, value=6 ) dayofweek = st.sidebar.slider( "Day of Week: 0=Monday, 6=Sunday", min_value=0, max_value=6, value=2 ) weekofyear = st.sidebar.slider( "Week of Year", min_value=1, max_value=53, value=25 ) # Use median values from training data for lag features default_lag_1 = float(train_df["lag_1_hour"].median()) if "lag_1_hour" in train_df.columns else 100.0 default_lag_24 = float(train_df["lag_24_hour"].median()) if "lag_24_hour" in train_df.columns else 100.0 default_roll_24 = float(train_df["rolling_24_hour_mean"].median()) if "rolling_24_hour_mean" in train_df.columns else 100.0 lag_1_hour = st.sidebar.number_input( "Previous Hour Meter Reading", min_value=0.0, value=round(default_lag_1, 2), step=10.0 ) lag_24_hour = st.sidebar.number_input( "Same Hour Previous Day Meter Reading", min_value=0.0, value=round(default_lag_24, 2), step=10.0 ) rolling_24_hour_mean = st.sidebar.number_input( "Previous 24-Hour Average Meter Reading", min_value=0.0, value=round(default_roll_24, 2), step=10.0 ) # ----------------------------- # Derived features # ----------------------------- is_weekend = 1 if dayofweek in [5, 6] else 0 is_business_hour = 1 if 8 <= hour <= 18 else 0 is_peak_hour = 1 if 12 <= hour <= 17 else 0 is_night_hour = 1 if hour >= 22 or hour <= 5 else 0 cooling_degree = max(air_temperature - 22, 0) heating_degree = max(18 - air_temperature, 0) comfortable_weather = 1 if 18 <= air_temperature <= 24 else 0 is_hot_weather = 1 if air_temperature > 28 else 0 is_cold_weather = 1 if air_temperature < 15 else 0 energy_per_sqft = lag_1_hour / square_feet if square_feet > 0 else 0 temp_peak_hour = air_temperature * is_peak_hour cooling_peak_hour = cooling_degree * is_peak_hour cooling_business_hour = cooling_degree * is_business_hour square_feet_cooling = square_feet * cooling_degree # Create prediction input row # ----------------------------- input_data = pd.DataFrame( [{col: 0.0 for col in feature_cols}], dtype="float64" ) # Direct numeric features manual_values = { "building_id": building_id, "meter": meter, "square_feet": square_feet, "air_temperature": air_temperature, "cloud_coverage": cloud_coverage, "dew_temperature": dew_temperature, "precip_depth_1_hr": precip_depth_1_hr, "sea_level_pressure": sea_level_pressure, "wind_direction": wind_direction, "wind_speed": wind_speed, "hour": hour, "day": day, "month": month, "dayofweek": dayofweek, "is_weekend": is_weekend, "weekofyear": weekofyear, "is_business_hour": is_business_hour, "is_peak_hour": is_peak_hour, "is_night_hour": is_night_hour, "cooling_degree": cooling_degree, "heating_degree": heating_degree, "comfortable_weather": comfortable_weather, "is_hot_weather": is_hot_weather, "is_cold_weather": is_cold_weather, "energy_per_sqft": energy_per_sqft, "lag_1_hour": lag_1_hour, "lag_24_hour": lag_24_hour, "rolling_24_hour_mean": rolling_24_hour_mean, "temp_peak_hour": temp_peak_hour, "cooling_peak_hour": cooling_peak_hour, "cooling_business_hour": cooling_business_hour, "square_feet_cooling": square_feet_cooling } for col, value in manual_values.items(): if col in input_data.columns: input_data.loc[0, col] = value # One-hot encoded primary_use columns primary_use_col = "primary_use_" + primary_use if primary_use_col in input_data.columns: input_data.loc[0, primary_use_col] = 1 # One-hot encoded meter_name column # Since drop_first=True was used, usually only meter_name_Electricity may exist. if meter_label == "Electricity": if "meter_name_Electricity" in input_data.columns: input_data.loc[0, "meter_name_Electricity"] = 1 elif meter_label == "Chilled Water": if "meter_name_Electricity" in input_data.columns: input_data.loc[0, "meter_name_Electricity"] = 0 # Ensure correct column order input_data = input_data[feature_cols] # ----------------------------- # Prediction # ----------------------------- st.subheader("Prediction Result") st.button("Refresh Prediction") if True: prediction = model.predict(input_data)[0] prediction = max(prediction, 0) # Demand category based on simple practical thresholds # You can adjust these later based on quantiles from prediction output if prediction < train_df["meter_reading"].quantile(0.33): demand_category = "Low Demand" elif prediction < train_df["meter_reading"].quantile(0.66): demand_category = "Medium Demand" else: demand_category = "High Demand" col1, col2, col3 = st.columns(3) with col1: st.metric("Predicted Meter Reading", f"{prediction:.2f}") with col2: st.metric("Meter Type", meter_label) with col3: st.metric("Demand Category", demand_category) # Recommendation logic def generate_recommendation(): if meter == 1 and demand_category == "High Demand" and cooling_degree > 3 and is_peak_hour == 1: return "High cooling demand during peak hours. Recommend pre-cooling before peak period and optimizing HVAC setpoint." elif meter == 1 and demand_category == "High Demand" and is_night_hour == 1: return "High chilled water demand during night hours. Investigate unnecessary cooling or HVAC scheduling inefficiency." elif meter == 0 and demand_category == "High Demand" and is_business_hour == 1: return "High electricity demand during business hours. Monitor HVAC and major equipment load." elif meter == 0 and demand_category == "High Demand" and is_night_hour == 1: return "High electricity demand during night hours. Investigate after-hours energy consumption." elif demand_category == "Medium Demand": return "Moderate demand. Continue monitoring and apply load balancing where possible." else: return "Normal demand. Maintain standard operating schedule." recommendation = generate_recommendation() st.subheader("HVAC Recommendation") st.info(recommendation) st.subheader("Derived HVAC Features") derived_df = pd.DataFrame({ "Feature": [ "Cooling Degree", "Heating Degree", "Business Hour", "Peak Hour", "Night Hour", "Weekend", "Energy per Sqft" ], "Value": [ cooling_degree, heating_degree, is_business_hour, is_peak_hour, is_night_hour, is_weekend, round(energy_per_sqft, 6) ] }) st.dataframe(derived_df, use_container_width=True) st.subheader("Model Input Preview") st.dataframe(input_data, use_container_width=True) # ----------------------------- # Model explanation: Feature importance # ----------------------------- st.markdown("---") with st.expander("Model explanation: Feature importance", expanded=False): st.write( "This chart shows which input features influenced the final prediction model the most." ) if hasattr(model, "feature_importances_"): importance_df = pd.DataFrame({ "Feature": feature_cols, "Importance": model.feature_importances_ }).sort_values("Importance", ascending=False) top_features = importance_df.head(10).copy() top_features = top_features.sort_values("Importance", ascending=True) st.bar_chart( top_features, x="Feature", y="Importance", horizontal=True ) st.caption( "lag_1_hour dominates because short-term building energy demand is strongly dependent on recent consumption." ) with st.expander("View feature importance table"): st.dataframe( importance_df.head(15), use_container_width=True ) else: st.info("Feature importance is not available for this model type.") # ----------------------------- # About section # ----------------------------- st.markdown("---") st.subheader("About This Project") st.write( """ This project uses machine learning to predict smart building energy demand using electricity and chilled water meter data, weather conditions, building information, time-based features, lag features, and HVAC-related indicators. The output supports energy management decisions by identifying high-demand situations and generating simple HVAC optimization recommendations. """ )