import streamlit as st import pandas as pd import numpy as np import plotly.express as px import plotly.graph_objects as go from datetime import datetime, timedelta from joblib import load import requests import pytz import time # Constants SUPABASE_URL = "https://ubbyirdtynaerjodadal.supabase.co" SUPABASE_API_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InViYnlpcmR0eW5hZXJqb2RhZGFsIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTI0OTIyNjcsImV4cCI6MjA2ODA2ODI2N30.iTHJ18BZED_gE5VyZrBp7YWiy6NNzsA1YdqeazFtxZI" TABLE = "smart_meter_readings_1year" TIMEZONE = pytz.timezone("Europe/London") now = pd.Timestamp.now(TIMEZONE) def auto_refresh(interval_seconds=60): time.sleep(interval_seconds) st.rerun() st.set_page_config(page_title="Electric Grid Dashboard", layout="wide") @st.cache_data(ttl=120) def load_data(): url = f"{SUPABASE_URL}/rest/v1/{TABLE}?timestamp=lt.{datetime.now().isoformat()}" headers = { "apikey": SUPABASE_API_KEY, "Authorization": f"Bearer {SUPABASE_API_KEY}" } res = requests.get(url, headers=headers) if res.status_code != 200: st.error(f"Failed to fetch data: {res.status_code}") st.stop() df = pd.DataFrame(res.json()) df['datetime'] = pd.to_datetime(df['timestamp'], utc=True) df['hour_of_day'] = df['datetime'].dt.hour df = df.set_index('datetime') df.sort_index(inplace=True) df['date'] = df.index.date df['week'] = df.index.isocalendar().week df['day_of_week'] = df.index.day_name() df['hour_sin'] = np.sin(2 * np.pi * df['hour_of_day'] / 24) df['hour_cos'] = np.cos(2 * np.pi * df['hour_of_day'] / 24) df['lag_30mins'] = df['power_consumption_kwh'].shift(1) df['lag_1hr'] = df['power_consumption_kwh'].shift(2) df['roll_mean_1hr'] = df['power_consumption_kwh'].shift(1).rolling(2).mean() df['roll_mean_2hr'] = df['power_consumption_kwh'].shift(1).rolling(4).mean() df[['lag_30mins', 'lag_1hr', 'roll_mean_1hr', 'roll_mean_2hr']] = df[[ 'lag_30mins', 'lag_1hr', 'roll_mean_1hr', 'roll_mean_2hr' ]].ffill().fillna(0) df = df.drop(columns=['date', 'hour_of_day']) df = pd.get_dummies(df, columns=['region', 'property_type', 'day_of_week'], drop_first=False) df = df.astype({col: 'int' for col in df.select_dtypes('bool').columns}) return df def main(): # Load data and model data = load_data() model = load('rf_model.pkl') # Generate forecasts features = data.drop(columns=['power_consumption_kwh', 'timestamp'], errors='ignore') data['forecast'] = model.predict(features[model.feature_names_in_]) # Calculate performance metrics latest_data = data.loc[data.index > pd.Timestamp.now(TIMEZONE) - pd.Timedelta('1D')] rmse = np.sqrt((latest_data['power_consumption_kwh'] - latest_data['forecast'])**2).mean() mae = (latest_data['power_consumption_kwh'] - latest_data['forecast']).abs().mean() current_error = (data['power_consumption_kwh'].iloc[-1] - data['forecast'].iloc[-1]) / data['power_consumption_kwh'].iloc[-1] * 100 # Title and description st.title("🌡️ Real-Time Energy Dashboard") st.markdown("Monitoring power consumption, environmental factors, and forecast accuracy across regions") # Sidebar filters st.sidebar.header("Filter Options") # Build readable region and property_type filters region_columns = list(data.filter(like='region_').columns) region_labels = ['All'] + [col.replace('region_', '') for col in region_columns] region = st.sidebar.selectbox("Region", region_labels) property_columns = list(data.filter(like='property_type_').columns) property_labels = ['All'] + [col.replace('property_type_', '') for col in property_columns] property_selection = st.sidebar.selectbox("Property Type", property_labels) time_range = st.sidebar.select_slider("Time Range", options=['1h', '6h', '12h', '1D', '1W'], value='12h') filtered_data = data.copy() # Apply region filter if region != 'All': region_col = f"region_{region}" if region_col in filtered_data.columns: filtered_data = filtered_data[filtered_data[region_col] == 1] # Apply property_type filter if property_selection != 'All': property_col = f"property_type_{property_selection}" if property_col in filtered_data.columns: filtered_data = filtered_data[filtered_data[property_col] == 1] # Apply time filter filtered_data = filtered_data.loc[filtered_data.index > now - pd.Timedelta(time_range)] #filtered_data = filtered_data.loc[filtered_data.index > pd.Timestamp.now(tz='UTC') - pd.Timedelta(time_range)] # Current metrics current = filtered_data.iloc[-1] # show metrics here st.subheader("📊 Current Energy Status") col1, col2, col3, col4 = st.columns(4) col1.metric("Power Consumption", f"{current['power_consumption_kwh']:.2f} kWh", delta=f"{current_error:.1f}% error", delta_color="inverse") col2.metric("Voltage", f"{current['voltage']:.1f} V") col3.metric("Temperature", f"{current['temperature_c']:.1f}°C") col4.metric("Humidity", f"{current['humidity_pct']:.1f}%") # --- 2-Hour Forecast --- st.subheader("🔮 Next 2 Hours Forecast") latest_row = data.iloc[-1:].copy() forecast_steps = [] timestamps = [] for i in range(1, 5): # 4 steps = next 2 hours (30-min intervals) future_time = latest_row.index[0] + timedelta(minutes=30 * i) timestamps.append(future_time) hour = future_time.hour hour_sin = np.sin(2 * np.pi * hour / 24) hour_cos = np.cos(2 * np.pi * hour / 24) new_row = latest_row.copy() new_row.index = [future_time] new_row['hour_sin'] = hour_sin new_row['hour_cos'] = hour_cos # Lags and rolling values if i == 1: lag_30 = latest_row['power_consumption_kwh'].values[0] lag_1hr = latest_row['lag_30mins'].values[0] roll_1hr = np.mean([lag_30, lag_1hr]) roll_2hr = np.mean([lag_30, lag_1hr, latest_row['lag_1hr'].values[0], latest_row['roll_mean_1hr'].values[0]]) else: lag_30 = forecast_steps[-1] lag_1hr = forecast_steps[-2] if i > 2 else latest_row['power_consumption_kwh'].values[0] roll_1hr = np.mean([lag_30, lag_1hr]) roll_2hr = np.mean(forecast_steps[-3:] + [lag_1hr]) if i > 3 else roll_1hr new_row['lag_30mins'] = lag_30 new_row['lag_1hr'] = lag_1hr new_row['roll_mean_1hr'] = roll_1hr new_row['roll_mean_2hr'] = roll_2hr X_future = new_row[model.feature_names_in_] y_pred = model.predict(X_future)[0] forecast_steps.append(y_pred) # Format forecast results forecast_df = pd.DataFrame({ "datetime": timestamps, "forecast_kwh": forecast_steps }).set_index("datetime") # --- Display 30 min / 1 hr / 2 hr Forecast --- col1, col2, col3 = st.columns(3) col1.metric("In 30 mins", f"{forecast_steps[0]:.2f} kWh", timestamps[0].strftime('%H:%M')) col2.metric("In 1 hour", f"{forecast_steps[1]:.2f} kWh", timestamps[1].strftime('%H:%M')) col3.metric("In 2 hours", f"{forecast_steps[3]:.2f} kWh", timestamps[3].strftime('%H:%M')) # Plot forecast fig_forecast = go.Figure() fig_forecast.add_trace(go.Scatter(x=forecast_df.index, y=forecast_df['forecast_kwh'], mode='lines+markers', name="Forecast")) fig_forecast.update_layout(title="2-Hour Ahead Forecast", xaxis_title="Time", yaxis_title="kWh") st.plotly_chart(fig_forecast, use_container_width=True) # Performance metrics # Model Performance: Current and 12-Hour Highs/Lows --- st.subheader("📏 Model Performance (Last 12 Hours, 30-Min Intervals)") # Step 1: Prepare error columns perf_df = data[['power_consumption_kwh', 'forecast']].copy() perf_df['error'] = perf_df['power_consumption_kwh'] - perf_df['forecast'] perf_df['abs_error'] = perf_df['error'].abs() perf_df['squared_error'] = perf_df['error']**2 # Step 2: Resample into 30-min intervals interval_perf = perf_df.resample('30min').agg({ 'squared_error': 'mean', 'abs_error': 'mean' }).dropna() # Limit to last 12 hours end_time = interval_perf.index.max() start_time = end_time -timedelta(hours=12) last_12h_perf = interval_perf.loc[start_time:end_time].copy() last_12h_perf['RMSE'] = np.sqrt(last_12h_perf['squared_error']) last_12h_perf['MAE'] = last_12h_perf['abs_error'] last_12h_perf = last_12h_perf[['RMSE', 'MAE']] # Step 3: Current metrics current_rmse = last_12h_perf['RMSE'].iloc[-1] current_mae = last_12h_perf['MAE'].iloc[-1] current_time = last_12h_perf.index[-1].strftime('%Y-%m-%d %H:%M') # Step 4: Highs and lows lowest_rmse = last_12h_perf['RMSE'].min() lowest_rmse_time = last_12h_perf['RMSE'].idxmin().strftime('%Y-%m-%d %H:%M') highest_rmse = last_12h_perf['RMSE'].max() highest_rmse_time = last_12h_perf['RMSE'].idxmax().strftime('%Y-%m-%d %H:%M') lowest_mae = last_12h_perf['MAE'].min() lowest_mae_time = last_12h_perf['MAE'].idxmin().strftime('%Y-%m-%d %H:%M') highest_mae = last_12h_perf['MAE'].max() highest_mae_time = last_12h_perf['MAE'].idxmax().strftime('%Y-%m-%d %H:%M') # Step 5: Display col1, col2 = st.columns(2) col1.metric("Current RMSE", f"{current_rmse:.3f} kWh", current_time) col2.metric("Current MAE", f"{current_mae:.3f} kWh", current_time) col3, col4, col5, col6 = st.columns(4) col3.metric("🔽 Lowest RMSE (12h)", f"{lowest_rmse:.3f} kWh", lowest_rmse_time) col4.metric("🔼 Highest RMSE (12h)", f"{highest_rmse:.3f} kWh", highest_rmse_time) col5.metric("🔽 Lowest MAE (12h)", f"{lowest_mae:.3f} kWh", lowest_mae_time) col6.metric("🔼 Highest MAE (12h)", f"{highest_mae:.3f} kWh", highest_mae_time) st.subheader("📈 RMSE and MAE over the Last 12 Hours") fig_errors = px.line( last_12h_perf, x=last_12h_perf.index, y=['RMSE', 'MAE'], labels={'value': 'Error (kWh)', 'variable': 'Metric', 'datetime': 'Time'}, title="Model Error Metrics (30-Min Intervals)" ) fig_errors.update_layout( xaxis_title="Time", yaxis_title="kWh", template="plotly_white", legend_title="Metric", height=350 ) st.plotly_chart(fig_errors, use_container_width=True) # Main content tabs tab1, tab2, tab3 = st.tabs(["Consumption Trends", "Regional Analysis", "Environmental Factors"]) with tab1: fig1 = px.line(filtered_data, x=filtered_data.index, y=['power_consumption_kwh', 'forecast'], title="Power Consumption vs Forecast") st.plotly_chart(fig1, use_container_width=True) # Hourly pattern numeric_cols = filtered_data.select_dtypes(include=[np.number]).columns hourly = filtered_data[numeric_cols].groupby(filtered_data.index.hour).mean() fig2 = px.bar(hourly, x=hourly.index, y='power_consumption_kwh', title="Average Hourly Consumption Pattern") st.plotly_chart(fig2, use_container_width=True) with tab2: if 'region' in data.columns: region_breakdown = data.groupby('region')['power_consumption_kwh'].sum().reset_index() fig3 = px.pie(region_breakdown, names='region', values='power_consumption_kwh', title="Regional Consumption Share") st.plotly_chart(fig3, use_container_width=True) # Regional comparison if len(data.filter(like='region_').columns) > 0: region_cols = data.filter(like='region_').columns region_avg = data[region_cols].mean().reset_index() region_avg.columns = ['Region', 'Avg Consumption'] fig4 = px.bar(region_avg, x='Region', y='Avg Consumption', title="Average Consumption by Region") st.plotly_chart(fig4, use_container_width=True) with tab3: fig5 = px.line(filtered_data, x=filtered_data.index, y=['temperature_c', 'humidity_pct'], title="Temperature & Humidity Trends") st.plotly_chart(fig5, use_container_width=True) fig6 = px.scatter(filtered_data, x='temperature_c', y='power_consumption_kwh', color='voltage', size='humidity_pct', title="Consumption vs Temperature (Colored by Voltage)") st.plotly_chart(fig6, use_container_width=True) # Footer st.markdown("---") st.markdown('Developed by Opeyemi Abodunrin') st.markdown(f"Last updated: {datetime.now(TIMEZONE).strftime('%Y-%m-%d %H:%M:%S')}") st.markdown("© 2025 Electric Forecast (Demonstration Purpose)") if __name__ == "__main__": main() auto_refresh(60)